From 527b5c1ca52d42e31496d37c7b7df431278e47c7 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 26 Mar 2020 14:39:07 +0100 Subject: [PATCH 001/109] Add SmbGetter --- get.go | 1 + get_file.go | 9 +++++++++ get_smb.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 get_smb.go diff --git a/get.go b/get.go index c82f52f05..e75067c62 100644 --- a/get.go +++ b/get.go @@ -73,6 +73,7 @@ func init() { Getters = map[string]Getter{ "file": new(FileGetter), + "smb": new(SmbGetter), "git": new(GitGetter), "gcs": new(GCSGetter), "hg": new(HgGetter), diff --git a/get_file.go b/get_file.go index f11d13ecf..ed242f111 100644 --- a/get_file.go +++ b/get_file.go @@ -19,7 +19,10 @@ func (g *FileGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.RawPath != "" { path = u.RawPath } + return mode(path) +} +func mode(path string) (Mode, error) { fi, err := os.Stat(path) if err != nil { return 0, err @@ -38,7 +41,10 @@ func (g *FileGetter) Get(ctx context.Context, req *Request) error { if req.u.RawPath != "" { path = req.u.RawPath } + return get(path, req) +} +func get(path string, req *Request) error { // The source path must exist and be a directory to be usable. if fi, err := os.Stat(path); err != nil { return fmt.Errorf("source path error: %s", err) @@ -82,7 +88,10 @@ func (g *FileGetter) GetFile(ctx context.Context, req *Request) error { if req.u.RawPath != "" { path = req.u.RawPath } + return getFile(path, req, ctx) +} +func getFile(path string, req *Request, ctx context.Context) error { // The source path must exist and be a file to be usable. if fi, err := os.Stat(path); err != nil { return fmt.Errorf("source path error: %s", err) diff --git a/get_smb.go b/get_smb.go new file mode 100644 index 000000000..52c94ad70 --- /dev/null +++ b/get_smb.go @@ -0,0 +1,36 @@ +package getter + +import ( + "context" + "net/url" +) + +// SmbGetter is a Getter implementation that will download a module from +// a shared folder using samba scheme. +type SmbGetter struct { + getter +} + +func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { + path := "//" + u.Host + u.Path + if u.RawPath != "" { + path = u.RawPath + } + return mode(path) +} + +func (g *SmbGetter) Get(ctx context.Context, req *Request) error { + path := "//" + req.u.Host + req.u.Path + if req.u.RawPath != "" { + path = req.u.RawPath + } + return get(path, req) +} + +func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { + path := "//" + req.u.Host + req.u.Path + if req.u.RawPath != "" { + path = req.u.RawPath + } + return getFile(path, req, ctx) +} From 1fd6e4dd847be6604e2af4e08c030b9273d3d77a Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 27 Mar 2020 11:19:32 +0100 Subject: [PATCH 002/109] Validate smb path contains host and filepath, add tests --- get_smb.go | 11 +++++ get_smb_test.go | 110 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 get_smb_test.go diff --git a/get_smb.go b/get_smb.go index 52c94ad70..672b5bbaf 100644 --- a/get_smb.go +++ b/get_smb.go @@ -2,6 +2,7 @@ package getter import ( "context" + "fmt" "net/url" ) @@ -10,8 +11,12 @@ import ( type SmbGetter struct { getter } +const pathError = "samba path should contain valid Host and filepath (smb:///)" func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { + if u.Host == "" || u.Path == "" { + return ModeFile, fmt.Errorf(pathError) + } path := "//" + u.Host + u.Path if u.RawPath != "" { path = u.RawPath @@ -20,6 +25,9 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { } func (g *SmbGetter) Get(ctx context.Context, req *Request) error { + if req.u.Host == "" || req.u.Path == ""{ + return fmt.Errorf(pathError) + } path := "//" + req.u.Host + req.u.Path if req.u.RawPath != "" { path = req.u.RawPath @@ -28,6 +36,9 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { } func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { + if req.u.Host == "" || req.u.Path == ""{ + return fmt.Errorf(pathError) + } path := "//" + req.u.Host + req.u.Path if req.u.RawPath != "" { path = req.u.RawPath diff --git a/get_smb_test.go b/get_smb_test.go new file mode 100644 index 000000000..144b179b0 --- /dev/null +++ b/get_smb_test.go @@ -0,0 +1,110 @@ +package getter + +import ( + "context" + urlhelper "github.com/hashicorp/go-getter/helper/url" + "testing" +) + +func TestSmbGetter_impl(t *testing.T) { + var _ Getter = new(SmbGetter) +} + +func TestSmbGetter_Get(t *testing.T) { + g := new(SmbGetter) + ctx := context.Background() + + // no hostname provided + url, err := urlhelper.Parse("smb://") + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + req := &Request{ + u: url, + } + if err := g.Get(ctx, req); err == nil { + t.Fatalf("err: should fail when request url doesn't have a Host") + } + if err := g.GetFile(ctx, req); err != nil && err.Error() != pathError { + t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) + } + + // no filepath provided + url, err = urlhelper.Parse("smb://host") + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + req = &Request{ + u: url, + } + if err := g.Get(ctx, req); err == nil { + t.Fatalf("err: should fail when request url doesn't have a Host") + } + if err := g.GetFile(ctx, req); err != nil && err.Error() != pathError { + t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) + } +} + +func TestSmbGetter_GetFile(t *testing.T) { + g := new(SmbGetter) + ctx := context.Background() + + // no hostname provided + url, err := urlhelper.Parse("smb://") + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + req := &Request{ + u: url, + } + if err := g.Get(ctx, req); err == nil { + t.Fatalf("err: should fail when request url doesn't have a Host") + } + if err := g.GetFile(ctx, req); err != nil && err.Error() != pathError { + t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) + } + + // no filepath provided + url, err = urlhelper.Parse("smb://host") + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + req = &Request{ + u: url, + } + if err := g.Get(ctx, req); err == nil { + t.Fatalf("err: should fail when request url doesn't have a Host") + } + if err := g.GetFile(ctx, req); err != nil && err.Error() != pathError { + t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) + } +} + +func TestSmbGetter_Mode(t *testing.T) { + g := new(SmbGetter) + ctx := context.Background() + + // no hostname provided + url, err := urlhelper.Parse("smb://") + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + if _, err := g.Mode(ctx, url); err == nil { + t.Fatalf("err: should fail when request url doesn't have a Host") + } + if _, err := g.Mode(ctx, url); err != nil && err.Error() != pathError { + t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) + } + + // no filepath provided + url, err = urlhelper.Parse("smb://") + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + if _, err := g.Mode(ctx, url); err == nil { + t.Fatalf("err: should fail when request url doesn't have a Host") + } + if _, err := g.Mode(ctx, url); err != nil && err.Error() != pathError { + t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) + } +} From 9fa293221a574c06eb32a2689bc145f58f37ca5c Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 27 Mar 2020 11:21:25 +0100 Subject: [PATCH 003/109] Fix format --- get_smb.go | 5 +++-- get_smb_test.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/get_smb.go b/get_smb.go index 672b5bbaf..b4ff53d28 100644 --- a/get_smb.go +++ b/get_smb.go @@ -11,6 +11,7 @@ import ( type SmbGetter struct { getter } + const pathError = "samba path should contain valid Host and filepath (smb:///)" func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -25,7 +26,7 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { } func (g *SmbGetter) Get(ctx context.Context, req *Request) error { - if req.u.Host == "" || req.u.Path == ""{ + if req.u.Host == "" || req.u.Path == "" { return fmt.Errorf(pathError) } path := "//" + req.u.Host + req.u.Path @@ -36,7 +37,7 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { } func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { - if req.u.Host == "" || req.u.Path == ""{ + if req.u.Host == "" || req.u.Path == "" { return fmt.Errorf(pathError) } path := "//" + req.u.Host + req.u.Path diff --git a/get_smb_test.go b/get_smb_test.go index 144b179b0..a347fd646 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -83,7 +83,7 @@ func TestSmbGetter_GetFile(t *testing.T) { func TestSmbGetter_Mode(t *testing.T) { g := new(SmbGetter) ctx := context.Background() - + // no hostname provided url, err := urlhelper.Parse("smb://") if err != nil { From 2931f375103cbcf8cbbf5005f9fbd56083c60e4b Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:17:06 +0200 Subject: [PATCH 004/109] Test circleci with samba server --- .circleci/config.old.yml | 61 ++++++++++++++++++++ .circleci/config.yml | 84 +++++++++++++++++++-------- get_smb.go | 99 +++++++++++++++++++++++++++----- get_smb_test.go | 121 ++++++++++++++++++++++----------------- go.mod | 9 +-- go.sum | 2 + 6 files changed, 277 insertions(+), 99 deletions(-) create mode 100644 .circleci/config.old.yml diff --git a/.circleci/config.old.yml b/.circleci/config.old.yml new file mode 100644 index 000000000..89dc974e2 --- /dev/null +++ b/.circleci/config.old.yml @@ -0,0 +1,61 @@ +version: 2.1 + +references: + images: + go: &GOLANG_IMAGE circleci/golang:latest + environments: + tmp: &TEST_RESULTS_PATH /tmp/test-results # path to where test results are saved + +# reusable 'executor' object for jobs +executors: + go: + docker: + - image: *GOLANG_IMAGE + environment: + - TEST_RESULTS: *TEST_RESULTS_PATH + +jobs: + go-fmt-and-test: + executor: go + steps: + - checkout + - run: mkdir -p $TEST_RESULTS + + # Restore go module cache if there is one + - restore_cache: + keys: + - go-getter-modcache-v1-{{ checksum "go.mod" }} + + - run: go mod download + + # Save go module cache if the go.mod file has changed + - save_cache: + key: go-getter-modcache-v1-{{ checksum "go.mod" }} + paths: + - "/go/pkg/mod" + + # check go fmt output because it does not report non-zero when there are fmt changes + - run: + name: check go fmt + command: | + files=$(go fmt ./...) + if [ -n "$files" ]; then + echo "The following file(s) do not conform to go fmt:" + echo "$files" + exit 1 + fi + + # run go tests with gotestsum + - run: | + PACKAGE_NAMES=$(go list ./...) + gotestsum --format=short-verbose --junitfile $TEST_RESULTS/gotestsum-report.xml -- $PACKAGE_NAMES + - store_test_results: + path: *TEST_RESULTS_PATH + - store_artifacts: + path: *TEST_RESULTS_PATH + +workflows: + version: 2 + test-and-build: + jobs: + - go-fmt-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml index 89dc974e2..996255049 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,25 +15,10 @@ executors: - TEST_RESULTS: *TEST_RESULTS_PATH jobs: - go-fmt-and-test: + go-fmt: executor: go steps: - checkout - - run: mkdir -p $TEST_RESULTS - - # Restore go module cache if there is one - - restore_cache: - keys: - - go-getter-modcache-v1-{{ checksum "go.mod" }} - - - run: go mod download - - # Save go module cache if the go.mod file has changed - - save_cache: - key: go-getter-modcache-v1-{{ checksum "go.mod" }} - paths: - - "/go/pkg/mod" - # check go fmt output because it does not report non-zero when there are fmt changes - run: name: check go fmt @@ -45,14 +30,65 @@ jobs: exit 1 fi - # run go tests with gotestsum - - run: | - PACKAGE_NAMES=$(go list ./...) - gotestsum --format=short-verbose --junitfile $TEST_RESULTS/gotestsum-report.xml -- $PACKAGE_NAMES - - store_test_results: - path: *TEST_RESULTS_PATH - - store_artifacts: - path: *TEST_RESULTS_PATH + go-test: + executor: go + steps: + docker: + - image: docker:18.03.0-ce-git + steps: + - checkout + - setup_remote_docker + - run: mkdir -p $TEST_RESULTS + + # Restore go module cache if there is one + - restore_cache: + keys: + - go-getter-modcache-v1-{{ checksum "go.mod" }} + + - run: + name: Start smb server + command: | + docker run -d --name samba \ + -e USERID=0 \ + -e GROUPID=0 \ + -p 137:137/udp \ + -p 138:138/udp \ + -p 139:139 \ + -p 445:445 \ + dperson/samba \ + -n \ + -u "username;password" \ + -s "shared;/data" + docker exec -it samba bash -c "cd data && touch file.txt" + docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" + + - run: + name: Start go-getter container + command: | + docker run -v $(pwd):/go-getter \ + -v $TEST_RESULTS:$TEST_RESULTS \ + --env TEST_RESULTS \ + --env PACKAGE_NAMES=$(go list ./...) + -w="/go-getter" \ + --link samba -t -d --name gogetter \ + circleci/golang:latest + + - run: docker exec -it gogetter bash -c "go mod download && sudo apt-get -y install smbclient" + + # Save go module cache if the go.mod file has changed + - save_cache: + key: go-getter-modcache-v1-{{ checksum "go.mod" }} + paths: + - "/go/pkg/mod" + + # run go tests with gotestsum + - run: | + docker exec -it gogetter bash -c "gotestsum --format=short-verbose --junitfile $TEST_RESULTS/gotestsum-report.xml -- $PACKAGE_NAMES" + + - store_test_results: + path: *TEST_RESULTS_PATH + - store_artifacts: + path: *TEST_RESULTS_PATH workflows: version: 2 diff --git a/get_smb.go b/get_smb.go index b4ff53d28..5bc699c7a 100644 --- a/get_smb.go +++ b/get_smb.go @@ -3,7 +3,12 @@ package getter import ( "context" "fmt" + "log" "net/url" + "os/exec" + "regexp" + "runtime" + "strings" ) // SmbGetter is a Getter implementation that will download a module from @@ -12,11 +17,12 @@ type SmbGetter struct { getter } -const pathError = "samba path should contain valid Host and filepath (smb:///)" +const basePathError = "samba path should contain valid Host and filepath (smb:///)" +// TODO: validate mode from smb path instead of stat func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.Host == "" || u.Path == "" { - return ModeFile, fmt.Errorf(pathError) + return ModeFile, fmt.Errorf(basePathError) } path := "//" + u.Host + u.Path if u.RawPath != "" { @@ -25,24 +31,91 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return mode(path) } +// TODO: also copy directory func (g *SmbGetter) Get(ctx context.Context, req *Request) error { - if req.u.Host == "" || req.u.Path == "" { - return fmt.Errorf(pathError) + hostPath, filePath, err := g.findHostAndFilePath(req) + log.Printf("MOSS hostPath %s filepath %s", hostPath, filePath) + if err == nil { + err = g.smbclientGetFile(hostPath, filePath, req) + } + + if err != nil && err.Error() == basePathError { + return err } - path := "//" + req.u.Host + req.u.Path - if req.u.RawPath != "" { - path = req.u.RawPath + + // Look for local mount of shared folder + if err != nil && runtime.GOOS == "windows" { + err = get(hostPath, req) } - return get(path, req) + + // throw error msg to install smbclient or mount shared folder + return err } func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { + hostPath, filePath, err := g.findHostAndFilePath(req) + if err == nil { + err = g.smbclientGetFile(hostPath, filePath, req) + } + + if err != nil && err.Error() == basePathError { + return err + } + + // Look for local mount of shared folder + if err != nil && runtime.GOOS == "windows" { + err = getFile(hostPath, req, ctx) + } + + // throw error msg to install smbclient or mount shared folder + return err +} + +func (g *SmbGetter) findHostAndFilePath(req *Request) (string, string, error) { if req.u.Host == "" || req.u.Path == "" { - return fmt.Errorf(pathError) + return "", "", fmt.Errorf(basePathError) + } + // Host path + hostPath := "//" + req.u.Host + + // Get shared directory + path := strings.TrimPrefix(req.u.Path, "/") + splt := regexp.MustCompile(`/`) + directories := splt.Split(path, 2) + + if len(directories) > 0 { + hostPath = hostPath + "/" + directories[0] + } + + // Check file path + if len(directories) <= 1 || directories[1] == "" { + return "", "", fmt.Errorf("can not find file path and/or name in the smb url") } - path := "//" + req.u.Host + req.u.Path - if req.u.RawPath != "" { - path = req.u.RawPath + + return hostPath, directories[1], nil +} + +func (g *SmbGetter) smbclientGetFile(hostPath string, fileDir string, req *Request) error { + file := "" + if strings.Contains(fileDir, "/") { + i := strings.LastIndex(fileDir, "/") + file = fileDir[i+1 : len(fileDir)-1] + fileDir = fileDir[0:i] + } else { + file = fileDir + fileDir = "." } - return getFile(path, req, ctx) + + // Get auth user and password + auth := req.u.User.Username() + if password, ok := req.u.User.Password(); ok { + auth = auth + "%" + password + } + + // Execute smbclient command + getCommand := fmt.Sprintf("'get %s'", file) + cmd := exec.Command("smbclient", "-U", auth, hostPath, "--directory", fileDir, "-c", getCommand) + log.Printf("Moss BEFORE EXECUTING COMMANDS") + cmd.Dir = req.Dst + return getRunCommand(cmd) } diff --git a/get_smb_test.go b/get_smb_test.go index a347fd646..37bfd85c7 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -10,59 +10,46 @@ func TestSmbGetter_impl(t *testing.T) { var _ Getter = new(SmbGetter) } +// make the structure func TestSmbGetter_Get(t *testing.T) { g := new(SmbGetter) ctx := context.Background() - // no hostname provided - url, err := urlhelper.Parse("smb://") + // correct url with auth data + url, err := urlhelper.Parse("smb://vagrant:vagrant@samba/shared/file.txt") if err != nil { t.Fatalf("err: %s", err.Error()) } req := &Request{ u: url, } - if err := g.Get(ctx, req); err == nil { - t.Fatalf("err: should fail when request url doesn't have a Host") - } - if err := g.GetFile(ctx, req); err != nil && err.Error() != pathError { - t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) + if err := g.Get(ctx, req); err != nil { + t.Fatalf("err: should not fail %s", err.Error()) } - // no filepath provided - url, err = urlhelper.Parse("smb://host") + //correct url with auth data and subdir + url, err = urlhelper.Parse("smb://vagrant:vagrant@samba/shared/subdir/file.txt") if err != nil { t.Fatalf("err: %s", err.Error()) } req = &Request{ u: url, } - if err := g.Get(ctx, req); err == nil { - t.Fatalf("err: should fail when request url doesn't have a Host") + if err := g.Get(ctx, req); err != nil { + t.Fatalf("err: should not fail: %s", err.Error()) } - if err := g.GetFile(ctx, req); err != nil && err.Error() != pathError { - t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) - } -} - -func TestSmbGetter_GetFile(t *testing.T) { - g := new(SmbGetter) - ctx := context.Background() // no hostname provided - url, err := urlhelper.Parse("smb://") + url, err = urlhelper.Parse("smb://") if err != nil { t.Fatalf("err: %s", err.Error()) } - req := &Request{ + req = &Request{ u: url, } if err := g.Get(ctx, req); err == nil { t.Fatalf("err: should fail when request url doesn't have a Host") } - if err := g.GetFile(ctx, req); err != nil && err.Error() != pathError { - t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) - } // no filepath provided url, err = urlhelper.Parse("smb://host") @@ -75,36 +62,62 @@ func TestSmbGetter_GetFile(t *testing.T) { if err := g.Get(ctx, req); err == nil { t.Fatalf("err: should fail when request url doesn't have a Host") } - if err := g.GetFile(ctx, req); err != nil && err.Error() != pathError { - t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) - } } -func TestSmbGetter_Mode(t *testing.T) { - g := new(SmbGetter) - ctx := context.Background() - - // no hostname provided - url, err := urlhelper.Parse("smb://") - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - if _, err := g.Mode(ctx, url); err == nil { - t.Fatalf("err: should fail when request url doesn't have a Host") - } - if _, err := g.Mode(ctx, url); err != nil && err.Error() != pathError { - t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) - } +//func TestSmbGetter_GetFile(t *testing.T) { +// g := new(SmbGetter) +// ctx := context.Background() +// +// // no hostname provided +// url, err := urlhelper.Parse("smb://") +// if err != nil { +// t.Fatalf("err: %s", err.Error()) +// } +// req := &Request{ +// u: url, +// } +// if err := g.GetFile(ctx, req); err != nil && err.Error() != basePathError { +// t.Fatalf("err: expected error: %s\n but error was: %s", basePathError, err.Error()) +// } +// +// // no filepath provided +// url, err = urlhelper.Parse("smb://host") +// if err != nil { +// t.Fatalf("err: %s", err.Error()) +// } +// req = &Request{ +// u: url, +// } +// if err := g.GetFile(ctx, req); err != nil && err.Error() != basePathError { +// t.Fatalf("err: expected error: %s\n but error was: %s", basePathError, err.Error()) +// } +//} - // no filepath provided - url, err = urlhelper.Parse("smb://") - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - if _, err := g.Mode(ctx, url); err == nil { - t.Fatalf("err: should fail when request url doesn't have a Host") - } - if _, err := g.Mode(ctx, url); err != nil && err.Error() != pathError { - t.Fatalf("err: expected error: %s\n but error was: %s", pathError, err.Error()) - } -} +//func TestSmbGetter_Mode(t *testing.T) { +// g := new(SmbGetter) +// ctx := context.Background() +// +// // no hostname provided +// url, err := urlhelper.Parse("smb://") +// if err != nil { +// t.Fatalf("err: %s", err.Error()) +// } +// if _, err := g.Mode(ctx, url); err == nil { +// t.Fatalf("err: should fail when request url doesn't have a Host") +// } +// if _, err := g.Mode(ctx, url); err != nil && err.Error() != basePathError { +// t.Fatalf("err: expected error: %s\n but error was: %s", basePathError, err.Error()) +// } +// +// // no filepath provided +// url, err = urlhelper.Parse("smb://") +// if err != nil { +// t.Fatalf("err: %s", err.Error()) +// } +// if _, err := g.Mode(ctx, url); err == nil { +// t.Fatalf("err: should fail when request url doesn't have a Host") +// } +// if _, err := g.Mode(ctx, url); err != nil && err.Error() != basePathError { +// t.Fatalf("err: expected error: %s\n but error was: %s", basePathError, err.Error()) +// } +//} diff --git a/go.mod b/go.mod index 3335e86a2..fdb259051 100644 --- a/go.mod +++ b/go.mod @@ -5,22 +5,15 @@ require ( github.com/aws/aws-sdk-go v1.15.78 github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d github.com/cheggaaa/pb v1.0.27 - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.7.0 // indirect github.com/google/go-cmp v0.3.0 github.com/hashicorp/go-cleanhttp v0.5.0 + github.com/hashicorp/go-getter v1.4.1 github.com/hashicorp/go-safetemp v1.0.0 github.com/hashicorp/go-version v1.1.0 - github.com/mattn/go-colorable v0.0.9 // indirect - github.com/mattn/go-isatty v0.0.4 // indirect - github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mitchellh/go-homedir v1.0.0 github.com/mitchellh/go-testing-interface v1.0.0 - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.2.2 // indirect github.com/ulikunitz/xz v0.5.5 google.golang.org/api v0.9.0 - gopkg.in/cheggaaa/pb.v1 v1.0.27 // indirect ) go 1.13 diff --git a/go.sum b/go.sum index b88c747ce..1f32e953d 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-getter v1.4.1 h1:3A2Mh8smGFcf5M+gmcv898mZdrxpseik45IpcyISLsA= +github.com/hashicorp/go-getter v1.4.1/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= From 8663295cb719e46c96a037716eee57fca5cace46 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:18:56 +0200 Subject: [PATCH 005/109] fix circleci config.yml --- .circleci/config.yml | 98 ++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 996255049..370f2849b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,64 +31,62 @@ jobs: fi go-test: - executor: go + docker: + - image: docker:18.03.0-ce-git steps: - docker: - - image: docker:18.03.0-ce-git - steps: - - checkout - - setup_remote_docker - - run: mkdir -p $TEST_RESULTS + - checkout + - setup_remote_docker + - run: mkdir -p $TEST_RESULTS - # Restore go module cache if there is one - - restore_cache: - keys: - - go-getter-modcache-v1-{{ checksum "go.mod" }} + # Restore go module cache if there is one + - restore_cache: + keys: + - go-getter-modcache-v1-{{ checksum "go.mod" }} - - run: - name: Start smb server - command: | - docker run -d --name samba \ - -e USERID=0 \ - -e GROUPID=0 \ - -p 137:137/udp \ - -p 138:138/udp \ - -p 139:139 \ - -p 445:445 \ - dperson/samba \ - -n \ - -u "username;password" \ - -s "shared;/data" - docker exec -it samba bash -c "cd data && touch file.txt" - docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" + - run: + name: Start smb server + command: | + docker run -d --name samba \ + -e USERID=0 \ + -e GROUPID=0 \ + -p 137:137/udp \ + -p 138:138/udp \ + -p 139:139 \ + -p 445:445 \ + dperson/samba \ + -n \ + -u "username;password" \ + -s "shared;/data" + docker exec -it samba bash -c "cd data && touch file.txt" + docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" - - run: - name: Start go-getter container - command: | - docker run -v $(pwd):/go-getter \ - -v $TEST_RESULTS:$TEST_RESULTS \ - --env TEST_RESULTS \ - --env PACKAGE_NAMES=$(go list ./...) - -w="/go-getter" \ - --link samba -t -d --name gogetter \ - circleci/golang:latest + - run: + name: Start go-getter container + command: | + docker run -v $(pwd):/go-getter \ + -v $TEST_RESULTS:$TEST_RESULTS \ + --env TEST_RESULTS \ + --env PACKAGE_NAMES=$(go list ./...) + -w="/go-getter" \ + --link samba -t -d --name gogetter \ + circleci/golang:latest - - run: docker exec -it gogetter bash -c "go mod download && sudo apt-get -y install smbclient" + - run: docker exec -it gogetter bash -c "go mod download && sudo apt-get -y install smbclient" - # Save go module cache if the go.mod file has changed - - save_cache: - key: go-getter-modcache-v1-{{ checksum "go.mod" }} - paths: - - "/go/pkg/mod" + # Save go module cache if the go.mod file has changed + - save_cache: + key: go-getter-modcache-v1-{{ checksum "go.mod" }} + paths: + - "/go/pkg/mod" - # run go tests with gotestsum - - run: | - docker exec -it gogetter bash -c "gotestsum --format=short-verbose --junitfile $TEST_RESULTS/gotestsum-report.xml -- $PACKAGE_NAMES" + # run go tests with gotestsum + - run: | + docker exec -it gogetter bash -c "gotestsum --format=short-verbose --junitfile $TEST_RESULTS/gotestsum-report.xml -- $PACKAGE_NAMES" - - store_test_results: - path: *TEST_RESULTS_PATH - - store_artifacts: - path: *TEST_RESULTS_PATH + - store_test_results: + path: *TEST_RESULTS_PATH + - store_artifacts: + path: *TEST_RESULTS_PATH workflows: version: 2 From dc15b4c8bdd006e6dadeb669dc7759fe7ff5360d Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:19:44 +0200 Subject: [PATCH 006/109] fix circleci config.yml --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 370f2849b..87c0773ae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -92,4 +92,5 @@ workflows: version: 2 test-and-build: jobs: - - go-fmt-and-test + - go-fmt + - go-test From 6aec1622b2efdb3f6f3032ce4bf523d4eee811de Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:25:23 +0200 Subject: [PATCH 007/109] fix env on circleci config.yml --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 87c0773ae..76cbe0ed2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: steps: - checkout - setup_remote_docker - - run: mkdir -p $TEST_RESULTS + - run: mkdir -p $TEST_RESULTS_PATH # Restore go module cache if there is one - restore_cache: @@ -65,7 +65,7 @@ jobs: command: | docker run -v $(pwd):/go-getter \ -v $TEST_RESULTS:$TEST_RESULTS \ - --env TEST_RESULTS \ + --env TEST_RESULTS_PATH \ --env PACKAGE_NAMES=$(go list ./...) -w="/go-getter" \ --link samba -t -d --name gogetter \ @@ -81,7 +81,7 @@ jobs: # run go tests with gotestsum - run: | - docker exec -it gogetter bash -c "gotestsum --format=short-verbose --junitfile $TEST_RESULTS/gotestsum-report.xml -- $PACKAGE_NAMES" + docker exec -it gogetter bash -c "gotestsum --format=short-verbose --junitfile $TEST_RESULTS_PATH/gotestsum-report.xml -- $PACKAGE_NAMES" - store_test_results: path: *TEST_RESULTS_PATH From dc7d1ce9d9682367d69bf0eee17ebfe03608cddb Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:29:47 +0200 Subject: [PATCH 008/109] fix env on circleci config.yml --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 76cbe0ed2..a679b56ba 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,8 +11,6 @@ executors: go: docker: - image: *GOLANG_IMAGE - environment: - - TEST_RESULTS: *TEST_RESULTS_PATH jobs: go-fmt: @@ -36,7 +34,9 @@ jobs: steps: - checkout - setup_remote_docker - - run: mkdir -p $TEST_RESULTS_PATH + - run: | + TEST_RESULTS_PATH=/tmp/test-results + mkdir -p $TEST_RESULTS_PATH # Restore go module cache if there is one - restore_cache: From 7219a8e044ec062d677abfd2190e767144bd5912 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:31:29 +0200 Subject: [PATCH 009/109] fix circleci config.yml one more time --- .circleci/config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a679b56ba..83829170e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,7 +66,6 @@ jobs: docker run -v $(pwd):/go-getter \ -v $TEST_RESULTS:$TEST_RESULTS \ --env TEST_RESULTS_PATH \ - --env PACKAGE_NAMES=$(go list ./...) -w="/go-getter" \ --link samba -t -d --name gogetter \ circleci/golang:latest @@ -81,7 +80,7 @@ jobs: # run go tests with gotestsum - run: | - docker exec -it gogetter bash -c "gotestsum --format=short-verbose --junitfile $TEST_RESULTS_PATH/gotestsum-report.xml -- $PACKAGE_NAMES" + docker exec -it gogetter bash -c "gotestsum --format=short-verbose --junitfile $TEST_RESULTS_PATH/gotestsum-report.xml -- $(go list ./...)" - store_test_results: path: *TEST_RESULTS_PATH From 84b332e43eca27f63453d9c5cb20e3c3f40e3e52 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:32:57 +0200 Subject: [PATCH 010/109] fix circleci config.yml at docker volume definition --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 83829170e..314bf6522 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,7 +64,7 @@ jobs: name: Start go-getter container command: | docker run -v $(pwd):/go-getter \ - -v $TEST_RESULTS:$TEST_RESULTS \ + -v $TEST_RESULTS_PATH:$TEST_RESULTS_PATH \ --env TEST_RESULTS_PATH \ -w="/go-getter" \ --link samba -t -d --name gogetter \ From e4c17c5547dd98eb6a946a111698fc2a2936cbb9 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:34:47 +0200 Subject: [PATCH 011/109] fix circleci config.yml at docker volume definition --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 314bf6522..27553cc1e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,7 +64,7 @@ jobs: name: Start go-getter container command: | docker run -v $(pwd):/go-getter \ - -v $TEST_RESULTS_PATH:$TEST_RESULTS_PATH \ + -v $(TEST_RESULTS_PATH):$(TEST_RESULTS_PATH) \ --env TEST_RESULTS_PATH \ -w="/go-getter" \ --link samba -t -d --name gogetter \ From 04bbbaa5458f34bd11f9a71ef2eb958a82edb236 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:35:48 +0200 Subject: [PATCH 012/109] fix circleci config.yml at docker volume definition --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 27553cc1e..61d5510bf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,7 +64,7 @@ jobs: name: Start go-getter container command: | docker run -v $(pwd):/go-getter \ - -v $(TEST_RESULTS_PATH):$(TEST_RESULTS_PATH) \ + -v /tmp/test-results:/tmp/test-results \ --env TEST_RESULTS_PATH \ -w="/go-getter" \ --link samba -t -d --name gogetter \ From 95e4f30222e3fc308f01ec148ba4492143d8a7b3 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:50:07 +0200 Subject: [PATCH 013/109] cd to go-getter before go mod --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 61d5510bf..10e988ca9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,7 +70,7 @@ jobs: --link samba -t -d --name gogetter \ circleci/golang:latest - - run: docker exec -it gogetter bash -c "go mod download && sudo apt-get -y install smbclient" + - run: docker exec -it gogetter bash -c "cd /go-getter && go mod download && sudo apt-get -y install smbclient" # Save go module cache if the go.mod file has changed - save_cache: From 5213d924938ea04ba2e2730725da46d4d3400f1c Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 15:52:12 +0200 Subject: [PATCH 014/109] remove go mod --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 10e988ca9..721e2de7b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,7 +70,7 @@ jobs: --link samba -t -d --name gogetter \ circleci/golang:latest - - run: docker exec -it gogetter bash -c "cd /go-getter && go mod download && sudo apt-get -y install smbclient" + - run: docker exec -it gogetter bash -c "sudo apt-get -y install smbclient" # Save go module cache if the go.mod file has changed - save_cache: From ce3e38e0c18b74e90e0f6095dffc97e2e66aadbd Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 16:10:00 +0200 Subject: [PATCH 015/109] split smb test --- .circleci/config.yml | 54 ++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 721e2de7b..fefad796b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,10 +13,25 @@ executors: - image: *GOLANG_IMAGE jobs: - go-fmt: + go-fmt-test: executor: go steps: - checkout + - run: mkdir -p $TEST_RESULTS + + # Restore go module cache if there is one + - restore_cache: + keys: + - go-getter-modcache-v1-{{ checksum "go.mod" }} + + - run: go mod download + + # Save go module cache if the go.mod file has changed + - save_cache: + key: go-getter-modcache-v1-{{ checksum "go.mod" }} + paths: + - "/go/pkg/mod" + # check go fmt output because it does not report non-zero when there are fmt changes - run: name: check go fmt @@ -28,20 +43,21 @@ jobs: exit 1 fi - go-test: + # run go tests with gotestsum + - run: | + PACKAGE_NAMES=$(go list ./...) + gotestsum --format=short-verbose --junitfile $TEST_RESULTS/gotestsum-report.xml -- $PACKAGE_NAMES + - store_test_results: + path: *TEST_RESULTS_PATH + - store_artifacts: + path: *TEST_RESULTS_PATH + + go-smb-test: docker: - image: docker:18.03.0-ce-git steps: - checkout - setup_remote_docker - - run: | - TEST_RESULTS_PATH=/tmp/test-results - mkdir -p $TEST_RESULTS_PATH - - # Restore go module cache if there is one - - restore_cache: - keys: - - go-getter-modcache-v1-{{ checksum "go.mod" }} - run: name: Start smb server @@ -59,37 +75,27 @@ jobs: -s "shared;/data" docker exec -it samba bash -c "cd data && touch file.txt" docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" + docker exec -it samba bash -c "ls data" - run: name: Start go-getter container command: | docker run -v $(pwd):/go-getter \ -v /tmp/test-results:/tmp/test-results \ - --env TEST_RESULTS_PATH \ -w="/go-getter" \ --link samba -t -d --name gogetter \ circleci/golang:latest - run: docker exec -it gogetter bash -c "sudo apt-get -y install smbclient" - # Save go module cache if the go.mod file has changed - - save_cache: - key: go-getter-modcache-v1-{{ checksum "go.mod" }} - paths: - - "/go/pkg/mod" - # run go tests with gotestsum - run: | - docker exec -it gogetter bash -c "gotestsum --format=short-verbose --junitfile $TEST_RESULTS_PATH/gotestsum-report.xml -- $(go list ./...)" + docker exec -it gogetter bash -c "go test ./... -run=TestSmbGetter_" - - store_test_results: - path: *TEST_RESULTS_PATH - - store_artifacts: - path: *TEST_RESULTS_PATH workflows: version: 2 test-and-build: jobs: - - go-fmt - - go-test + - go-fmt-test + - go-smb-test From ba4819df0c839530012d5427133be4226f31329e Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 16:11:45 +0200 Subject: [PATCH 016/109] fix circleci config.yml --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fefad796b..5700589ef 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,9 +11,11 @@ executors: go: docker: - image: *GOLANG_IMAGE + environment: + - TEST_RESULTS: *TEST_RESULTS_PATH jobs: - go-fmt-test: + go-fmt-and-test: executor: go steps: - checkout @@ -97,5 +99,5 @@ workflows: version: 2 test-and-build: jobs: - - go-fmt-test + - go-fmt-and-test - go-smb-test From 80ad6b3a6e2fbc6a335f6cd966a615217017f614 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 16:21:13 +0200 Subject: [PATCH 017/109] remove logs and test docker exec --- .circleci/config.yml | 4 +--- get_smb.go | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5700589ef..aab59f4de 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -90,9 +90,7 @@ jobs: - run: docker exec -it gogetter bash -c "sudo apt-get -y install smbclient" - # run go tests with gotestsum - - run: | - docker exec -it gogetter bash -c "go test ./... -run=TestSmbGetter_" + - run: docker exec -it gogetter bash -c "ls -la" workflows: diff --git a/get_smb.go b/get_smb.go index 5bc699c7a..939437119 100644 --- a/get_smb.go +++ b/get_smb.go @@ -3,7 +3,6 @@ package getter import ( "context" "fmt" - "log" "net/url" "os/exec" "regexp" @@ -34,7 +33,6 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { // TODO: also copy directory func (g *SmbGetter) Get(ctx context.Context, req *Request) error { hostPath, filePath, err := g.findHostAndFilePath(req) - log.Printf("MOSS hostPath %s filepath %s", hostPath, filePath) if err == nil { err = g.smbclientGetFile(hostPath, filePath, req) } @@ -115,7 +113,6 @@ func (g *SmbGetter) smbclientGetFile(hostPath string, fileDir string, req *Reque // Execute smbclient command getCommand := fmt.Sprintf("'get %s'", file) cmd := exec.Command("smbclient", "-U", auth, hostPath, "--directory", fileDir, "-c", getCommand) - log.Printf("Moss BEFORE EXECUTING COMMANDS") cmd.Dir = req.Dst return getRunCommand(cmd) } From e6096b23879591149cfc7135245dd6c562384e0f Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 16:24:53 +0200 Subject: [PATCH 018/109] add working dir --- .circleci/config.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index aab59f4de..3937c44ac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,6 +55,7 @@ jobs: path: *TEST_RESULTS_PATH go-smb-test: + working_directory: /go-getter docker: - image: docker:18.03.0-ce-git steps: @@ -82,15 +83,17 @@ jobs: - run: name: Start go-getter container command: | - docker run -v $(pwd):/go-getter \ + docker run -v /go-getter:/go-getter \ -v /tmp/test-results:/tmp/test-results \ -w="/go-getter" \ --link samba -t -d --name gogetter \ circleci/golang:latest - - run: docker exec -it gogetter bash -c "sudo apt-get -y install smbclient" + - run: docker exec -it gogetter bash -c "ls && cd /go-getter && ls" - - run: docker exec -it gogetter bash -c "ls -la" + - run: + name: Run smb tests + command: docker exec -it gogetter bash -c "go test ./... -run=TestSmbGetter_" workflows: From 7fb57034aa43b504b90f5f02a97c69900b7b0e59 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 16:29:48 +0200 Subject: [PATCH 019/109] add testing docker cmd --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3937c44ac..31fb424d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -89,7 +89,7 @@ jobs: --link samba -t -d --name gogetter \ circleci/golang:latest - - run: docker exec -it gogetter bash -c "ls && cd /go-getter && ls" + - run: docker exec -it gogetter bash -c "ls /go-getter" - run: name: Run smb tests From 446b3797a545f0747c79368d23961dc3ec4f7f2d Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 17:00:14 +0200 Subject: [PATCH 020/109] Add docker compose --- .circleci/config.yml | 40 ++++++++++++---------------------------- docker-compose.yml | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 docker-compose.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 31fb424d9..68d322b14 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,43 +57,27 @@ jobs: go-smb-test: working_directory: /go-getter docker: - - image: docker:18.03.0-ce-git + - image: docker:17.05.0-ce-git steps: - checkout - setup_remote_docker - - run: - name: Start smb server + name: Install dependencies command: | - docker run -d --name samba \ - -e USERID=0 \ - -e GROUPID=0 \ - -p 137:137/udp \ - -p 138:138/udp \ - -p 139:139 \ - -p 445:445 \ - dperson/samba \ - -n \ - -u "username;password" \ - -s "shared;/data" - docker exec -it samba bash -c "cd data && touch file.txt" - docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" - docker exec -it samba bash -c "ls data" - + apk add --no-cache \ + py-pip=9.0.0-r1 + pip install \ + docker-compose==1.12.0 - run: - name: Start go-getter container + name: Start smb server command: | - docker run -v /go-getter:/go-getter \ - -v /tmp/test-results:/tmp/test-results \ - -w="/go-getter" \ - --link samba -t -d --name gogetter \ - circleci/golang:latest - - - run: docker exec -it gogetter bash -c "ls /go-getter" - + docker-compose up -d samba + - run: sleep 60 - run: name: Run smb tests - command: docker exec -it gogetter bash -c "go test ./... -run=TestSmbGetter_" + command: | + docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" + docker-compose up gogetter workflows: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..a65555f27 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3.2' + +services: + samba: + image: dperson/samba + container_name: samba + restart: unless-stopped + environment: + USERID: "0" + GROUPID: "0" + networks: + - default + ports: + - "137:137/udp" # required to advertise shares (NMBD) + - "138:138/udp" # required to advertise shares (NMBD) + - "139:139/tcp" # default smb port + - "445:445/tcp" # default smb port + read_only: false + tmpfs: + - /tmp + volumes: + - /mnt:/mnt:z # :z allows share to be used by multiple containers + command: '-s "shared;/data"' + + gogetter: + image: circleci/golang:latest + container_name: gogetter + depends_on: + - samba + networks: + - default + volumes: + - /go-getter:/go-getter + working_dir: /go-getter + command: bash -c "go mod download && sudo apt-get -y install smbclient && go test ./... -run=TestSmbGetter_" + +networks: + default: \ No newline at end of file From 61404b94fe74ce33305ac6aa8e995c87a25a2c00 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 17:54:48 +0200 Subject: [PATCH 021/109] change golang image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index a65555f27..006cb4078 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,7 +23,7 @@ services: command: '-s "shared;/data"' gogetter: - image: circleci/golang:latest + image: golang:latest container_name: gogetter depends_on: - samba From 6de5e77cc34e1b97829bf20f33b77a85fd762e48 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 18:08:41 +0200 Subject: [PATCH 022/109] add ls to circleci config --- .circleci/config.yml | 1 + docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 68d322b14..ec502e9ca 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -78,6 +78,7 @@ jobs: command: | docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" docker-compose up gogetter + docker exec -it gogetter bash -c "ls" workflows: diff --git a/docker-compose.yml b/docker-compose.yml index 006cb4078..ff1a35bb4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: volumes: - /go-getter:/go-getter working_dir: /go-getter - command: bash -c "go mod download && sudo apt-get -y install smbclient && go test ./... -run=TestSmbGetter_" + command: bash -c "go mod download && apt-get update && apt-get -y install smbclient && go test ./... -run=TestSmbGetter_" networks: default: \ No newline at end of file From 315b03f7d12df94239968ba9411c892c054be2cb Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 18:14:55 +0200 Subject: [PATCH 023/109] sakda --- .circleci/config.yml | 2 -- docker-compose.yml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ec502e9ca..cf302f989 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -78,8 +78,6 @@ jobs: command: | docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" docker-compose up gogetter - docker exec -it gogetter bash -c "ls" - workflows: version: 2 diff --git a/docker-compose.yml b/docker-compose.yml index ff1a35bb4..2b39878cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: volumes: - /go-getter:/go-getter working_dir: /go-getter - command: bash -c "go mod download && apt-get update && apt-get -y install smbclient && go test ./... -run=TestSmbGetter_" + command: bash -c "ls" networks: default: \ No newline at end of file From 89e52e9855cd2bad5b9695ea065d94adcead07e9 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 18:18:33 +0200 Subject: [PATCH 024/109] try 1 --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cf302f989..e11170da1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -77,7 +77,8 @@ jobs: name: Run smb tests command: | docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" - docker-compose up gogetter + docker exec -it samba bash -c "ls data" + docker-compose up --verbose gogetter workflows: version: 2 From b70d6cb34db0ddcb4f71f8d902d722b3663174d4 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 18:21:50 +0200 Subject: [PATCH 025/109] try 2 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e11170da1..140998239 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -78,7 +78,7 @@ jobs: command: | docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" docker exec -it samba bash -c "ls data" - docker-compose up --verbose gogetter + docker-compose --verbose up gogetter workflows: version: 2 From 65c6cd66e0691df30021337c3257b1fcd8dbf90f Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 18:30:24 +0200 Subject: [PATCH 026/109] try one more time --- .circleci/config.yml | 14 ++++++++++---- docker-compose.yml | 1 - 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 140998239..aa6bdac7a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,7 +59,6 @@ jobs: docker: - image: docker:17.05.0-ce-git steps: - - checkout - setup_remote_docker - run: name: Install dependencies @@ -68,17 +67,24 @@ jobs: py-pip=9.0.0-r1 pip install \ docker-compose==1.12.0 + - run: name: Start smb server command: | docker-compose up -d samba - run: sleep 60 + - + - run: + name: Start go-getter container + command: | + docker-compose -t -d up gogetter + + - checkout - run: - name: Run smb tests + name: run tests command: | docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" - docker exec -it samba bash -c "ls data" - docker-compose --verbose up gogetter + docker exec -it gogetter bash -c "ls && go mod download && apt-get update && apt-get -y install smbclient && go test ./... -run=TestSmbGetter_" workflows: version: 2 diff --git a/docker-compose.yml b/docker-compose.yml index 2b39878cb..91ed15658 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,6 @@ services: volumes: - /go-getter:/go-getter working_dir: /go-getter - command: bash -c "ls" networks: default: \ No newline at end of file From 03e65b3db65abd836d21c907f193301bc640a230 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 18:32:48 +0200 Subject: [PATCH 027/109] remove weird char --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index aa6bdac7a..955e6ce7d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -73,7 +73,7 @@ jobs: command: | docker-compose up -d samba - run: sleep 60 - - + - run: name: Start go-getter container command: | From baf26a96eabe9572da522171dec12b10b295bae3 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 16 Apr 2020 18:37:18 +0200 Subject: [PATCH 028/109] one last think --- .circleci/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 955e6ce7d..c635ea0e4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,6 +59,7 @@ jobs: docker: - image: docker:17.05.0-ce-git steps: + - checkout - setup_remote_docker - run: name: Install dependencies @@ -73,16 +74,16 @@ jobs: command: | docker-compose up -d samba - run: sleep 60 - + - run: name: Start go-getter container command: | docker-compose -t -d up gogetter - - checkout - run: name: run tests command: | + docker cp /go-getter/. gogetter:/go-getter/ docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" docker exec -it gogetter bash -c "ls && go mod download && apt-get update && apt-get -y install smbclient && go test ./... -run=TestSmbGetter_" From 44f7ff831bc57cd065da2296f44f73348711781f Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 17 Apr 2020 11:02:32 +0200 Subject: [PATCH 029/109] run containers at the same time --- .circleci/config.yml | 14 +++++--------- docker-compose.yml | 3 +-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c635ea0e4..bc9408c01 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,21 +70,17 @@ jobs: docker-compose==1.12.0 - run: - name: Start smb server + name: Start smb server and gogetter containers command: | - docker-compose up -d samba - - run: sleep 60 + docker-compose up -d - - run: - name: Start go-getter container - command: | - docker-compose -t -d up gogetter + - run: sleep 60 - run: name: run tests command: | - docker cp /go-getter/. gogetter:/go-getter/ - docker exec -it samba bash -c "cd data && mkdir subdir && cd subdir && touch file.txt" + docker cp /go-getter/ gogetter:/go-getter/ + docker exec -it samba bash -c "cd data && touch file.txt && mkdir subdir && cd subdir && touch file.txt" docker exec -it gogetter bash -c "ls && go mod download && apt-get update && apt-get -y install smbclient && go test ./... -run=TestSmbGetter_" workflows: diff --git a/docker-compose.yml b/docker-compose.yml index 91ed15658..46aa08039 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,9 +29,8 @@ services: - samba networks: - default - volumes: - - /go-getter:/go-getter working_dir: /go-getter + command: tail -f /dev/null networks: default: \ No newline at end of file From 4d3aa87547ef26561a065fa49ed124c964cac1e7 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 17 Apr 2020 11:19:47 +0200 Subject: [PATCH 030/109] skip smb tests when env var is not set --- .circleci/config.yml | 4 ++-- get_smb_test.go | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bc9408c01..57779fd90 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -79,9 +79,9 @@ jobs: - run: name: run tests command: | - docker cp /go-getter/ gogetter:/go-getter/ + docker cp ./ gogetter:/go-getter/ docker exec -it samba bash -c "cd data && touch file.txt && mkdir subdir && cd subdir && touch file.txt" - docker exec -it gogetter bash -c "ls && go mod download && apt-get update && apt-get -y install smbclient && go test ./... -run=TestSmbGetter_" + docker exec -it gogetter bash -c "ls && go mod download && apt-get update && apt-get -y install smbclient && env ACC_SMB_TEST=1 go test ./... -run=TestSmbGetter_" workflows: version: 2 diff --git a/get_smb_test.go b/get_smb_test.go index 37bfd85c7..9c2cfb614 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -3,6 +3,7 @@ package getter import ( "context" urlhelper "github.com/hashicorp/go-getter/helper/url" + "os" "testing" ) @@ -12,6 +13,8 @@ func TestSmbGetter_impl(t *testing.T) { // make the structure func TestSmbGetter_Get(t *testing.T) { + smbTestsPreCheck(t) + g := new(SmbGetter) ctx := context.Background() @@ -64,6 +67,13 @@ func TestSmbGetter_Get(t *testing.T) { } } +func smbTestsPreCheck(t *testing.T) { + r := os.Getenv("ACC_SMB_TEST") + if r != "1" { + t.Skip("Smb getter tests won't run. ACC_SMB_TEST not set") + } +} + //func TestSmbGetter_GetFile(t *testing.T) { // g := new(SmbGetter) // ctx := context.Background() From 8308b6e332882a348480cb4d7515b2abf063d96c Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 17 Apr 2020 11:38:35 +0200 Subject: [PATCH 031/109] add failing test to see what happens --- .circleci/config.yml | 4 ++-- get_smb_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 57779fd90..ec03af5bd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,8 +80,8 @@ jobs: name: run tests command: | docker cp ./ gogetter:/go-getter/ - docker exec -it samba bash -c "cd data && touch file.txt && mkdir subdir && cd subdir && touch file.txt" - docker exec -it gogetter bash -c "ls && go mod download && apt-get update && apt-get -y install smbclient && env ACC_SMB_TEST=1 go test ./... -run=TestSmbGetter_" + docker exec -it samba bash -c "touch data/file.txt && mkdir -p data/subdir && touch data/subdir/file.txt" + docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient && env ACC_SMB_TEST=1 go test ./... -run=TestSmbGetter_" workflows: version: 2 diff --git a/get_smb_test.go b/get_smb_test.go index 9c2cfb614..82294285b 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -26,8 +26,8 @@ func TestSmbGetter_Get(t *testing.T) { req := &Request{ u: url, } - if err := g.Get(ctx, req); err != nil { - t.Fatalf("err: should not fail %s", err.Error()) + if err := g.Get(ctx, req); err == nil { + t.Fatalf("err: should not fail TEST MOSS") } //correct url with auth data and subdir From 24f6452d3064bd0ffe115793124b98807551d1a8 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 17 Apr 2020 15:53:51 +0200 Subject: [PATCH 032/109] test smbclient --- .circleci/config.old.yml | 61 ---------------------------------------- .circleci/config.yml | 14 +++++---- 2 files changed, 9 insertions(+), 66 deletions(-) delete mode 100644 .circleci/config.old.yml diff --git a/.circleci/config.old.yml b/.circleci/config.old.yml deleted file mode 100644 index 89dc974e2..000000000 --- a/.circleci/config.old.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: 2.1 - -references: - images: - go: &GOLANG_IMAGE circleci/golang:latest - environments: - tmp: &TEST_RESULTS_PATH /tmp/test-results # path to where test results are saved - -# reusable 'executor' object for jobs -executors: - go: - docker: - - image: *GOLANG_IMAGE - environment: - - TEST_RESULTS: *TEST_RESULTS_PATH - -jobs: - go-fmt-and-test: - executor: go - steps: - - checkout - - run: mkdir -p $TEST_RESULTS - - # Restore go module cache if there is one - - restore_cache: - keys: - - go-getter-modcache-v1-{{ checksum "go.mod" }} - - - run: go mod download - - # Save go module cache if the go.mod file has changed - - save_cache: - key: go-getter-modcache-v1-{{ checksum "go.mod" }} - paths: - - "/go/pkg/mod" - - # check go fmt output because it does not report non-zero when there are fmt changes - - run: - name: check go fmt - command: | - files=$(go fmt ./...) - if [ -n "$files" ]; then - echo "The following file(s) do not conform to go fmt:" - echo "$files" - exit 1 - fi - - # run go tests with gotestsum - - run: | - PACKAGE_NAMES=$(go list ./...) - gotestsum --format=short-verbose --junitfile $TEST_RESULTS/gotestsum-report.xml -- $PACKAGE_NAMES - - store_test_results: - path: *TEST_RESULTS_PATH - - store_artifacts: - path: *TEST_RESULTS_PATH - -workflows: - version: 2 - test-and-build: - jobs: - - go-fmt-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml index ec03af5bd..6e6c5fcee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -62,7 +62,7 @@ jobs: - checkout - setup_remote_docker - run: - name: Install dependencies + name: install dependencies command: | apk add --no-cache \ py-pip=9.0.0-r1 @@ -70,18 +70,22 @@ jobs: docker-compose==1.12.0 - run: - name: Start smb server and gogetter containers + name: start smb server and gogetter containers command: | docker-compose up -d - - run: sleep 60 + - run: + name: wait for containers to start + command: sleep 60 - run: - name: run tests + name: run smb getter tests command: | docker cp ./ gogetter:/go-getter/ docker exec -it samba bash -c "touch data/file.txt && mkdir -p data/subdir && touch data/subdir/file.txt" - docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient && env ACC_SMB_TEST=1 go test ./... -run=TestSmbGetter_" + docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient" +# docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient && env ACC_SMB_TEST=1 go test -v ./... -run=TestSmbGetter_" + docker exec -it gogetter bash -c "smbclient -N -U vagrant%vagrant //samba/shared --directory . -c 'get file.txt' && ls" workflows: version: 2 From c31df1219703f3a7223a74da3946594a8888b4ba Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 17 Apr 2020 15:55:37 +0200 Subject: [PATCH 033/109] remove comment --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6e6c5fcee..71b2ef258 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -84,7 +84,6 @@ jobs: docker cp ./ gogetter:/go-getter/ docker exec -it samba bash -c "touch data/file.txt && mkdir -p data/subdir && touch data/subdir/file.txt" docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient" -# docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient && env ACC_SMB_TEST=1 go test -v ./... -run=TestSmbGetter_" docker exec -it gogetter bash -c "smbclient -N -U vagrant%vagrant //samba/shared --directory . -c 'get file.txt' && ls" workflows: From 25417725fed5828a915a1b55a55b668c64a24bc8 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 17 Apr 2020 15:58:40 +0200 Subject: [PATCH 034/109] config back to normal --- .circleci/config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 71b2ef258..4540bcf39 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -83,8 +83,7 @@ jobs: command: | docker cp ./ gogetter:/go-getter/ docker exec -it samba bash -c "touch data/file.txt && mkdir -p data/subdir && touch data/subdir/file.txt" - docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient" - docker exec -it gogetter bash -c "smbclient -N -U vagrant%vagrant //samba/shared --directory . -c 'get file.txt' && ls" + docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient && env ACC_SMB_TEST=1 go test -v ./... -run=TestSmbGetter_" workflows: version: 2 From 95366d593a6974cff29c60ec2e1a7887f12793a7 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 17 Apr 2020 17:34:14 +0200 Subject: [PATCH 035/109] adds struct test and test cases --- get_smb.go | 53 ++++++++++++---- get_smb_test.go | 166 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 162 insertions(+), 57 deletions(-) diff --git a/get_smb.go b/get_smb.go index 939437119..2a86a9e8d 100644 --- a/get_smb.go +++ b/get_smb.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/url" + "os" "os/exec" "regexp" "runtime" @@ -33,20 +34,29 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { // TODO: also copy directory func (g *SmbGetter) Get(ctx context.Context, req *Request) error { hostPath, filePath, err := g.findHostAndFilePath(req) + if err == nil { err = g.smbclientGetFile(hostPath, filePath, req) + if err == nil { + return nil + } } + os.RemoveAll(req.Dst) - if err != nil && err.Error() == basePathError { + if err.Error() == basePathError { return err } // Look for local mount of shared folder - if err != nil && runtime.GOOS == "windows" { - err = get(hostPath, req) + if runtime.GOOS == "linux" { + hostPath = strings.TrimPrefix(hostPath, "/") + } + err = get(hostPath, req) + if err == nil { + return nil } - // throw error msg to install smbclient or mount shared folder + // TODO throw error msg to install smbclient or mount shared folder return err } @@ -97,22 +107,43 @@ func (g *SmbGetter) smbclientGetFile(hostPath string, fileDir string, req *Reque file := "" if strings.Contains(fileDir, "/") { i := strings.LastIndex(fileDir, "/") - file = fileDir[i+1 : len(fileDir)-1] - fileDir = fileDir[0:i] + file = fileDir[i+1:] + fileDir = fileDir[:i] } else { file = fileDir fileDir = "." } + smbcmd := "smbclient -N" + // Get auth user and password auth := req.u.User.Username() - if password, ok := req.u.User.Password(); ok { - auth = auth + "%" + password + if auth != "" { + if password, ok := req.u.User.Password(); ok { + auth = auth + "%" + password + } + smbcmd = smbcmd + " -U " + auth + } + + getFile := fmt.Sprintf("'get %s'", file) + smbcmd = smbcmd + " " + hostPath+ " --directory " + fileDir + " --command "+ getFile + cmd := exec.Command("bash","-c", smbcmd) + + if req.Dst != "" { + _, err := os.Lstat(req.Dst) + if err != nil { + if os.IsNotExist(err) { + // Create destination folder if it doesn't exists + if err := os.MkdirAll(req.Dst, os.ModePerm); err != nil { + return fmt.Errorf("failed to creat destination path: %s", err.Error()) + } + } else { + return err + } + } + cmd.Dir = req.Dst } // Execute smbclient command - getCommand := fmt.Sprintf("'get %s'", file) - cmd := exec.Command("smbclient", "-U", auth, hostPath, "--directory", fileDir, "-c", getCommand) - cmd.Dir = req.Dst return getRunCommand(cmd) } diff --git a/get_smb_test.go b/get_smb_test.go index 82294285b..3479c06c1 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -4,6 +4,7 @@ import ( "context" urlhelper "github.com/hashicorp/go-getter/helper/url" "os" + "path/filepath" "testing" ) @@ -11,59 +12,132 @@ func TestSmbGetter_impl(t *testing.T) { var _ Getter = new(SmbGetter) } -// make the structure +// TODO: +// tests: +// fails both local and smbclient +// save tests results on circleci +// allow download directory (?) +// update Mode to return right mode +// write GetFile tests +// write higher level tests + +type smbTest struct { + name string + rawURL string + file string + createFile string + fail bool +} + func TestSmbGetter_Get(t *testing.T) { smbTestsPreCheck(t) - g := new(SmbGetter) - ctx := context.Background() - - // correct url with auth data - url, err := urlhelper.Parse("smb://vagrant:vagrant@samba/shared/file.txt") - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - req := &Request{ - u: url, - } - if err := g.Get(ctx, req); err == nil { - t.Fatalf("err: should not fail TEST MOSS") + tests := []smbTest{ + { + "smbclient with authentication", + "smb://vagrant:vagrant@samba/shared/file.txt", + "file.txt", + "", + false, + }, + { + "smbclient with authentication and subdir", + "smb://vagrant:vagrant@samba/shared/subdir/file.txt", + "file.txt", + "", + false, + }, + { + "smbclient with only username authentication", + "smb://vagrant@samba/shared/file.txt", + "file.txt", + "", + false, + }, + { + "smbclient without authentication", + "smb://samba/shared/file.txt", + "file.txt", + "", + false, + }, + { + "local mounted smb shared file", + "smb://samba/shared/mounted.txt", + "mounted.txt", + "/samba/shared/mounted.txt", + false, + }, + { + "no hostname provided", + "smb://", + "", + "", + true, + }, + { + "no filepath provided", + "smb://samba", + "", + "", + true, + }, } - //correct url with auth data and subdir - url, err = urlhelper.Parse("smb://vagrant:vagrant@samba/shared/subdir/file.txt") - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - req = &Request{ - u: url, - } - if err := g.Get(ctx, req); err != nil { - t.Fatalf("err: should not fail: %s", err.Error()) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.createFile != "" { + // mock mounted folder by creating one + err := os.MkdirAll(tt.createFile, os.ModePerm) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + defer os.RemoveAll(tt.createFile) + } - // no hostname provided - url, err = urlhelper.Parse("smb://") - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - req = &Request{ - u: url, - } - if err := g.Get(ctx, req); err == nil { - t.Fatalf("err: should fail when request url doesn't have a Host") - } + dst := tempDir(t) + defer os.RemoveAll(dst) - // no filepath provided - url, err = urlhelper.Parse("smb://host") - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - req = &Request{ - u: url, - } - if err := g.Get(ctx, req); err == nil { - t.Fatalf("err: should fail when request url doesn't have a Host") + url, err := urlhelper.Parse(tt.rawURL) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + req := &Request{ + Dst: dst, + u: url, + } + + g := new(SmbGetter) + ctx := context.Background() + err = g.Get(ctx, req) + fail := err != nil + + if tt.fail != fail { + if fail { + t.Fatalf("err: unexpected error %s", err.Error()) + } + t.Fatalf("err: expecting to fail but it did not") + } + + if !tt.fail { + if tt.createFile != "" { + // Verify the destination folder is a symlink to mounted folder + fi, err := os.Lstat(dst) + if err != nil { + t.Fatalf("err: %s", err) + } + if fi.Mode()&os.ModeSymlink == 0 { + t.Fatal("destination is not a symlink") + } + } + + // Verify the file exists at the destination folder + mainPath := filepath.Join(dst, tt.file) + if _, err := os.Stat(mainPath); err != nil { + t.Fatalf("err: %s", err) + } + } + }) } } From 42ad4609334425b1add6fc5321ec37a552985b51 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 17 Apr 2020 17:37:51 +0200 Subject: [PATCH 036/109] fmt and undo some unecessary changes --- get_file.go | 3 --- get_smb.go | 15 ++++----------- get_smb_test.go | 10 ++++++---- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/get_file.go b/get_file.go index ed242f111..6a07d4f64 100644 --- a/get_file.go +++ b/get_file.go @@ -19,10 +19,7 @@ func (g *FileGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.RawPath != "" { path = u.RawPath } - return mode(path) -} -func mode(path string) (Mode, error) { fi, err := os.Stat(path) if err != nil { return 0, err diff --git a/get_smb.go b/get_smb.go index 2a86a9e8d..b0d0afbd3 100644 --- a/get_smb.go +++ b/get_smb.go @@ -19,16 +19,9 @@ type SmbGetter struct { const basePathError = "samba path should contain valid Host and filepath (smb:///)" -// TODO: validate mode from smb path instead of stat func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { - if u.Host == "" || u.Path == "" { - return ModeFile, fmt.Errorf(basePathError) - } - path := "//" + u.Host + u.Path - if u.RawPath != "" { - path = u.RawPath - } - return mode(path) + // TODO: validate mode from smb path instead of stat + return ModeFile, nil } // TODO: also copy directory @@ -126,8 +119,8 @@ func (g *SmbGetter) smbclientGetFile(hostPath string, fileDir string, req *Reque } getFile := fmt.Sprintf("'get %s'", file) - smbcmd = smbcmd + " " + hostPath+ " --directory " + fileDir + " --command "+ getFile - cmd := exec.Command("bash","-c", smbcmd) + smbcmd = smbcmd + " " + hostPath + " --directory " + fileDir + " --command " + getFile + cmd := exec.Command("bash", "-c", smbcmd) if req.Dst != "" { _, err := os.Lstat(req.Dst) diff --git a/get_smb_test.go b/get_smb_test.go index 3479c06c1..ccdd62d3a 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -33,13 +33,13 @@ func TestSmbGetter_Get(t *testing.T) { smbTestsPreCheck(t) tests := []smbTest{ - { - "smbclient with authentication", + { + "smbclient with authentication", "smb://vagrant:vagrant@samba/shared/file.txt", "file.txt", "", false, - }, + }, { "smbclient with authentication and subdir", "smb://vagrant:vagrant@samba/shared/subdir/file.txt", @@ -86,6 +86,8 @@ func TestSmbGetter_Get(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.createFile != "" { // mock mounted folder by creating one err := os.MkdirAll(tt.createFile, os.ModePerm) @@ -104,7 +106,7 @@ func TestSmbGetter_Get(t *testing.T) { } req := &Request{ Dst: dst, - u: url, + u: url, } g := new(SmbGetter) From 1bb624b722b78f7ea343def4a1653eb5803e6ea1 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 17 Apr 2020 18:17:27 +0200 Subject: [PATCH 037/109] remove dst folder when created on smbclient --- get_smb.go | 20 ++++++++++++++------ get_smb_test.go | 2 -- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/get_smb.go b/get_smb.go index b0d0afbd3..4cef86d3d 100644 --- a/get_smb.go +++ b/get_smb.go @@ -17,7 +17,7 @@ type SmbGetter struct { getter } -const basePathError = "samba path should contain valid Host and filepath (smb:///)" +const basePathError = "samba path should contain valid host, filepath, and authentication if necessary (smb://:@/)" func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { // TODO: validate mode from smb path instead of stat @@ -28,16 +28,25 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { func (g *SmbGetter) Get(ctx context.Context, req *Request) error { hostPath, filePath, err := g.findHostAndFilePath(req) + dstExisted := false + if req.Dst != "" { + if _, err := os.Lstat(req.Dst); err == nil { + dstExisted = true + } + } + if err == nil { err = g.smbclientGetFile(hostPath, filePath, req) if err == nil { return nil } } - os.RemoveAll(req.Dst) - if err.Error() == basePathError { - return err + if !dstExisted { + // Remove the destination created for smbclient files + if err := os.Remove(req.Dst); err != nil { + return err + } } // Look for local mount of shared folder @@ -49,8 +58,7 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { return nil } - // TODO throw error msg to install smbclient or mount shared folder - return err + return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) } func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { diff --git a/get_smb_test.go b/get_smb_test.go index ccdd62d3a..9f7ce2ff0 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -86,8 +86,6 @@ func TestSmbGetter_Get(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - t.Parallel() - if tt.createFile != "" { // mock mounted folder by creating one err := os.MkdirAll(tt.createFile, os.ModePerm) From 64fb0208afc8e9edd9578aa5a701a0ccc583ce35 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 10:14:06 +0200 Subject: [PATCH 038/109] add GetFile tests --- get_smb.go | 33 +++++++-- get_smb_test.go | 193 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 182 insertions(+), 44 deletions(-) diff --git a/get_smb.go b/get_smb.go index 4cef86d3d..c4b16018b 100644 --- a/get_smb.go +++ b/get_smb.go @@ -6,6 +6,7 @@ import ( "net/url" "os" "os/exec" + "path/filepath" "regexp" "runtime" "strings" @@ -24,7 +25,7 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return ModeFile, nil } -// TODO: also copy directory +// TODO: copy directory func (g *SmbGetter) Get(ctx context.Context, req *Request) error { hostPath, filePath, err := g.findHostAndFilePath(req) @@ -63,21 +64,39 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { hostPath, filePath, err := g.findHostAndFilePath(req) + + dstExisted := false + if req.Dst != "" { + if _, err := os.Lstat(req.Dst); err == nil { + dstExisted = true + } + } + if err == nil { err = g.smbclientGetFile(hostPath, filePath, req) + if err == nil { + return nil + } } - if err != nil && err.Error() == basePathError { - return err + if !dstExisted { + // Remove the destination created for smbclient files + if err := os.Remove(req.Dst); err != nil { + return err + } } // Look for local mount of shared folder - if err != nil && runtime.GOOS == "windows" { - err = getFile(hostPath, req, ctx) + if runtime.GOOS == "linux" { + hostPath = strings.TrimPrefix(hostPath, "/") + } + path := filepath.Join(hostPath, filePath) + err = getFile(path, req, ctx) + if err == nil { + return nil } - // throw error msg to install smbclient or mount shared folder - return err + return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) } func (g *SmbGetter) findHostAndFilePath(req *Request) (string, string, error) { diff --git a/get_smb_test.go b/get_smb_test.go index 9f7ce2ff0..f389520fb 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -3,6 +3,7 @@ package getter import ( "context" urlhelper "github.com/hashicorp/go-getter/helper/url" + "log" "os" "path/filepath" "testing" @@ -14,12 +15,11 @@ func TestSmbGetter_impl(t *testing.T) { // TODO: // tests: -// fails both local and smbclient -// save tests results on circleci // allow download directory (?) // update Mode to return right mode // write GetFile tests // write higher level tests +// save tests results on circleci type smbTest struct { name string @@ -88,10 +88,24 @@ func TestSmbGetter_Get(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.createFile != "" { // mock mounted folder by creating one - err := os.MkdirAll(tt.createFile, os.ModePerm) + err := os.MkdirAll(filepath.Dir(tt.createFile), 0755) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + + f, err := os.Create(tt.createFile) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + defer f.Close() + + // Write content to assert later + _, err = f.WriteString("Hello\n") if err != nil { t.Fatalf("err: %s", err.Error()) } + f.Sync() + defer os.RemoveAll(tt.createFile) } @@ -109,7 +123,7 @@ func TestSmbGetter_Get(t *testing.T) { g := new(SmbGetter) ctx := context.Background() - err = g.Get(ctx, req) + err = g.GetFile(ctx, req) fail := err != nil if tt.fail != fail { @@ -124,17 +138,151 @@ func TestSmbGetter_Get(t *testing.T) { // Verify the destination folder is a symlink to mounted folder fi, err := os.Lstat(dst) if err != nil { + log.Printf("MOSS err 1") t.Fatalf("err: %s", err) } if fi.Mode()&os.ModeSymlink == 0 { t.Fatal("destination is not a symlink") } + // Verify the main file exists + assertContents(t, dst, "Hello\n") + } else { + // Verify the file exists at the destination folder + mainPath := filepath.Join(dst, tt.file) + if _, err := os.Stat(mainPath); err != nil { + log.Printf("MOSS err 2") + t.Fatalf("err: %s", err) + } + } + } + }) + } +} + +func TestSmbGetter_GetFile(t *testing.T) { + smbTestsPreCheck(t) + + tests := []smbTest{ + { + "smbclient with authentication", + "smb://vagrant:vagrant@samba/shared/file.txt", + "file.txt", + "", + false, + }, + { + "smbclient with authentication and subdir", + "smb://vagrant:vagrant@samba/shared/subdir/file.txt", + "file.txt", + "", + false, + }, + { + "smbclient with only username authentication", + "smb://vagrant@samba/shared/file.txt", + "file.txt", + "", + false, + }, + { + "smbclient without authentication", + "smb://samba/shared/file.txt", + "file.txt", + "", + false, + }, + { + "local mounted smb shared file", + "smb://samba/shared/mounted.txt", + "mounted.txt", + "/samba/shared/mounted.txt", + false, + }, + { + "no hostname provided", + "smb://", + "", + "", + true, + }, + { + "no filepath provided", + "smb://samba", + "", + "", + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.createFile != "" { + // mock mounted folder by creating one + err := os.MkdirAll(filepath.Dir(tt.createFile), 0755) + if err != nil { + t.Fatalf("err: %s", err.Error()) } - // Verify the file exists at the destination folder - mainPath := filepath.Join(dst, tt.file) - if _, err := os.Stat(mainPath); err != nil { - t.Fatalf("err: %s", err) + f, err := os.Create(tt.createFile) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + defer f.Close() + + // Write content to assert later + _, err = f.WriteString("Hello\n") + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + f.Sync() + + defer os.RemoveAll(tt.createFile) + } + + dst := tempDir(t) + defer os.RemoveAll(dst) + + url, err := urlhelper.Parse(tt.rawURL) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + req := &Request{ + Dst: dst, + u: url, + } + + g := new(SmbGetter) + ctx := context.Background() + err = g.GetFile(ctx, req) + fail := err != nil + + if tt.fail != fail { + if fail { + t.Fatalf("err: unexpected error %s", err.Error()) + } + t.Fatalf("err: expecting to fail but it did not") + } + + if !tt.fail { + if tt.createFile != "" { + // Verify the destination folder is a symlink to mounted folder + fi, err := os.Lstat(dst) + if err != nil { + log.Printf("MOSS err 1") + t.Fatalf("err: %s", err) + } + if fi.Mode()&os.ModeSymlink == 0 { + t.Fatal("destination is not a symlink") + } + // Verify the main file exists + assertContents(t, dst, "Hello\n") + } else { + // Verify the file exists at the destination folder + mainPath := filepath.Join(dst, tt.file) + if _, err := os.Stat(mainPath); err != nil { + log.Printf("MOSS err 2") + t.Fatalf("err: %s", err) + } } } }) @@ -148,35 +296,6 @@ func smbTestsPreCheck(t *testing.T) { } } -//func TestSmbGetter_GetFile(t *testing.T) { -// g := new(SmbGetter) -// ctx := context.Background() -// -// // no hostname provided -// url, err := urlhelper.Parse("smb://") -// if err != nil { -// t.Fatalf("err: %s", err.Error()) -// } -// req := &Request{ -// u: url, -// } -// if err := g.GetFile(ctx, req); err != nil && err.Error() != basePathError { -// t.Fatalf("err: expected error: %s\n but error was: %s", basePathError, err.Error()) -// } -// -// // no filepath provided -// url, err = urlhelper.Parse("smb://host") -// if err != nil { -// t.Fatalf("err: %s", err.Error()) -// } -// req = &Request{ -// u: url, -// } -// if err := g.GetFile(ctx, req); err != nil && err.Error() != basePathError { -// t.Fatalf("err: expected error: %s\n but error was: %s", basePathError, err.Error()) -// } -//} - //func TestSmbGetter_Mode(t *testing.T) { // g := new(SmbGetter) // ctx := context.Background() From 679547ac762f65d12fd5af4f87dfab268e66a2f5 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 12:12:02 +0200 Subject: [PATCH 039/109] write smb mode --- get_file.go | 3 + get_smb.go | 237 ++++++++++++++++++++++++++++++++++-------------- get_smb_test.go | 26 ++++-- 3 files changed, 189 insertions(+), 77 deletions(-) diff --git a/get_file.go b/get_file.go index 6a07d4f64..ed242f111 100644 --- a/get_file.go +++ b/get_file.go @@ -19,7 +19,10 @@ func (g *FileGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.RawPath != "" { path = u.RawPath } + return mode(path) +} +func mode(path string) (Mode, error) { fi, err := os.Stat(path) if err != nil { return 0, err diff --git a/get_smb.go b/get_smb.go index c4b16018b..216ca5985 100644 --- a/get_smb.go +++ b/get_smb.go @@ -1,15 +1,16 @@ package getter import ( + "bytes" "context" "fmt" "net/url" "os" "os/exec" - "path/filepath" "regexp" "runtime" "strings" + "syscall" ) // SmbGetter is a Getter implementation that will download a module from @@ -21,14 +22,67 @@ type SmbGetter struct { const basePathError = "samba path should contain valid host, filepath, and authentication if necessary (smb://:@/)" func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { - // TODO: validate mode from smb path instead of stat + if u.Host == "" || u.Path == "" { + return 0, fmt.Errorf(basePathError) + } + // Look in possible local mount of shared folder + path := "/" + u.Host + u.Path + if runtime.GOOS == "windows" { + path = "/" + path + } + if m, err := mode(path); err == nil { + return m, nil + } + + // TODO write propper error msg + return g.smbClientMode(u) +} + +func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { + hostPath, fileDir, err := g.findHostAndFilePath(u) + if err != nil { + return 0, err + } + file := "" + // Get file and subdirectory name when existent + if strings.Contains(fileDir, "/") { + i := strings.LastIndex(fileDir, "/") + file = fileDir[i+1:] + fileDir = fileDir[:i] + } else { + file = fileDir + fileDir = "." + } + + getFileCmd := "smbclient -N" + + // Append auth user and password to cmd + auth := u.User.Username() + if auth != "" { + if password, ok := u.User.Password(); ok { + auth = auth + "%" + password + } + getFileCmd = getFileCmd + " -U " + auth + } + + baseCmd := getFileCmd + " " + hostPath + " --directory " + fileDir + + // check file exists in the smb shared folder to check the mode + isDir, err := isDirectory(baseCmd, file) + if err != nil { + return 0, err + } + if isDir { + return ModeDir, nil + } return ModeFile, nil } // TODO: copy directory func (g *SmbGetter) Get(ctx context.Context, req *Request) error { - hostPath, filePath, err := g.findHostAndFilePath(req) - + if req.u.Host == "" || req.u.Path == "" { + return fmt.Errorf(basePathError) + } dstExisted := false if req.Dst != "" { if _, err := os.Lstat(req.Dst); err == nil { @@ -36,35 +90,37 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { } } + // First look in a possible local mount of the shared folder + path := "/" + req.u.Host + req.u.Path + if runtime.GOOS == "windows" { + path = "/" + path + } + if err := get(path, req); err == nil { + return nil + } + + // Look for the file using smbclient cli + err := g.smbclientGetFile(req) if err == nil { - err = g.smbclientGetFile(hostPath, filePath, req) - if err == nil { - return nil - } + return nil } + err = fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) + if !dstExisted { // Remove the destination created for smbclient files - if err := os.Remove(req.Dst); err != nil { - return err + if rerr := os.Remove(req.Dst); rerr != nil { + err = fmt.Errorf("%s \n failed to remove created destination folder: %s", err.Error(), rerr.Error()) } } - // Look for local mount of shared folder - if runtime.GOOS == "linux" { - hostPath = strings.TrimPrefix(hostPath, "/") - } - err = get(hostPath, req) - if err == nil { - return nil - } - - return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) + return err } func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { - hostPath, filePath, err := g.findHostAndFilePath(req) - + if req.u.Host == "" || req.u.Path == "" { + return fmt.Errorf(basePathError) + } dstExisted := false if req.Dst != "" { if _, err := os.Lstat(req.Dst); err == nil { @@ -72,59 +128,40 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { } } - if err == nil { - err = g.smbclientGetFile(hostPath, filePath, req) - if err == nil { - return nil - } + // First look in a possible local mount of the shared folder + path := "/" + req.u.Host + req.u.Path + if runtime.GOOS == "windows" { + path = "/" + path } - - if !dstExisted { - // Remove the destination created for smbclient files - if err := os.Remove(req.Dst); err != nil { - return err - } + if err := getFile(path, req, ctx); err == nil { + return nil } - // Look for local mount of shared folder - if runtime.GOOS == "linux" { - hostPath = strings.TrimPrefix(hostPath, "/") - } - path := filepath.Join(hostPath, filePath) - err = getFile(path, req, ctx) + // Look for the file using smbclient cli + err := g.smbclientGetFile(req) if err == nil { return nil } - return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) -} - -func (g *SmbGetter) findHostAndFilePath(req *Request) (string, string, error) { - if req.u.Host == "" || req.u.Path == "" { - return "", "", fmt.Errorf(basePathError) - } - // Host path - hostPath := "//" + req.u.Host - - // Get shared directory - path := strings.TrimPrefix(req.u.Path, "/") - splt := regexp.MustCompile(`/`) - directories := splt.Split(path, 2) - - if len(directories) > 0 { - hostPath = hostPath + "/" + directories[0] - } + err = fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) - // Check file path - if len(directories) <= 1 || directories[1] == "" { - return "", "", fmt.Errorf("can not find file path and/or name in the smb url") + if !dstExisted { + // Remove the destination created for smbclient files + if rerr := os.Remove(req.Dst); rerr != nil { + err = fmt.Errorf("%s \n failed to remove created destination folder: %s", err.Error(), rerr.Error()) + } } - return hostPath, directories[1], nil + return err } -func (g *SmbGetter) smbclientGetFile(hostPath string, fileDir string, req *Request) error { +func (g *SmbGetter) smbclientGetFile(req *Request) error { + hostPath, fileDir, err := g.findHostAndFilePath(req.u) + if err != nil { + return err + } file := "" + // Get file and subdirectory name when existent if strings.Contains(fileDir, "/") { i := strings.LastIndex(fileDir, "/") file = fileDir[i+1:] @@ -134,20 +171,31 @@ func (g *SmbGetter) smbclientGetFile(hostPath string, fileDir string, req *Reque fileDir = "." } - smbcmd := "smbclient -N" + getFileCmd := "smbclient -N" - // Get auth user and password + // Append auth user and password to cmd auth := req.u.User.Username() if auth != "" { if password, ok := req.u.User.Password(); ok { auth = auth + "%" + password } - smbcmd = smbcmd + " -U " + auth + getFileCmd = getFileCmd + " -U " + auth + } + + baseCmd := getFileCmd + " " + hostPath + " --directory " + fileDir + + // check file exists in the smb shared folder and is not a directory + isDir, err := isDirectory(baseCmd, file) + if err != nil { + return err + } + if isDir { + return fmt.Errorf("%s is a directory", file) } - getFile := fmt.Sprintf("'get %s'", file) - smbcmd = smbcmd + " " + hostPath + " --directory " + fileDir + " --command " + getFile - cmd := exec.Command("bash", "-c", smbcmd) + // download file + getFileCmd = baseCmd + " --command " + fmt.Sprintf("'get %s'", file) + cmd := exec.Command("bash", "-c", getFileCmd) if req.Dst != "" { _, err := os.Lstat(req.Dst) @@ -164,6 +212,55 @@ func (g *SmbGetter) smbclientGetFile(hostPath string, fileDir string, req *Reque cmd.Dir = req.Dst } - // Execute smbclient command - return getRunCommand(cmd) + _, err = runSmbClientCommand(cmd) + return err +} + +func (g *SmbGetter) findHostAndFilePath(u *url.URL) (string, string, error) { + // Host path + hostPath := "//" + u.Host + + // Get shared directory + path := strings.TrimPrefix(u.Path, "/") + splt := regexp.MustCompile(`/`) + directories := splt.Split(path, 2) + + if len(directories) > 0 { + hostPath = hostPath + "/" + directories[0] + } + + // Check file path + if len(directories) <= 1 || directories[1] == "" { + return "", "", fmt.Errorf("can not find file path and/or name in the smb url") + } + + return hostPath, directories[1], nil +} + +func isDirectory(baseCmd string, file string) (bool, error) { + fileInfoCmd := baseCmd + " --command " + fmt.Sprintf("'allinfo %s'", file) + cmd := exec.Command("bash", "-c", fileInfoCmd) + output, err := runSmbClientCommand(cmd) + if err != nil { + return false, err + } + return strings.Contains(output, "attributes: D"), nil +} + +func runSmbClientCommand(cmd *exec.Cmd) (string, error) { + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + err := cmd.Run() + if exiterr, ok := err.(*exec.ExitError); ok { + // The program has exited with an exit code != 0 + if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { + return buf.String(), fmt.Errorf( + "%s exited with %d: %s", + cmd.Path, + status.ExitStatus(), + buf.String()) + } + } + return buf.String(), nil } diff --git a/get_smb_test.go b/get_smb_test.go index f389520fb..afbd4eb60 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -16,8 +16,6 @@ func TestSmbGetter_impl(t *testing.T) { // TODO: // tests: // allow download directory (?) -// update Mode to return right mode -// write GetFile tests // write higher level tests // save tests results on circleci @@ -171,7 +169,7 @@ func TestSmbGetter_GetFile(t *testing.T) { false, }, { - "smbclient with authentication and subdir", + "smbclient with authentication and subdirectory", "smb://vagrant:vagrant@samba/shared/subdir/file.txt", "file.txt", "", @@ -191,6 +189,20 @@ func TestSmbGetter_GetFile(t *testing.T) { "", false, }, + { + "smbclient get directory", + "smb://vagrant:vagrant@samba/shared/subdir", + "", + "", + true, + }, + { + "smbclient get non existent file", + "smb://vagrant:vagrant@samba/shared/nonexistent.txt", + "", + "", + true, + }, { "local mounted smb shared file", "smb://samba/shared/mounted.txt", @@ -252,10 +264,9 @@ func TestSmbGetter_GetFile(t *testing.T) { } g := new(SmbGetter) - ctx := context.Background() - err = g.GetFile(ctx, req) - fail := err != nil + err = g.GetFile(context.Background(), req) + fail := err != nil if tt.fail != fail { if fail { t.Fatalf("err: unexpected error %s", err.Error()) @@ -277,7 +288,8 @@ func TestSmbGetter_GetFile(t *testing.T) { // Verify the main file exists assertContents(t, dst, "Hello\n") } else { - // Verify the file exists at the destination folder + // Verify if the file was successfully download + // and exists at the destination folder mainPath := filepath.Join(dst, tt.file) if _, err := os.Stat(mainPath); err != nil { log.Printf("MOSS err 2") From 9cecc400a5801154ec2e4308f2f34f19f3a5864b Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 14:57:55 +0200 Subject: [PATCH 040/109] write smb mode tests --- README.md | 1 + get_smb.go | 30 +++++++++---- get_smb_test.go | 116 +++++++++++++++++++++++++++++++----------------- 3 files changed, 99 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index bbcd15de9..64f1bafff 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ can be augmented at runtime by implementing the `Getter` interface. * HTTP * Amazon S3 * Google GCP + * SMB In addition to the above protocols, go-getter has what are called "detectors." These take a URL and attempt to automatically choose the best protocol for diff --git a/get_smb.go b/get_smb.go index 216ca5985..c74ff516d 100644 --- a/get_smb.go +++ b/get_smb.go @@ -25,7 +25,8 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.Host == "" || u.Path == "" { return 0, fmt.Errorf(basePathError) } - // Look in possible local mount of shared folder + + // Look in a possible local mount of shared folder path := "/" + u.Host + u.Path if runtime.GOOS == "windows" { path = "/" + path @@ -34,8 +35,13 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return m, nil } - // TODO write propper error msg - return g.smbClientMode(u) + // If not mounted, use smbclient cli to verify mode + mode, err := g.smbClientMode(u) + if err == nil { + return mode, nil + } + + return 0, fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) } func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { @@ -121,6 +127,7 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { if req.u.Host == "" || req.u.Path == "" { return fmt.Errorf(basePathError) } + dstExisted := false if req.Dst != "" { if _, err := os.Lstat(req.Dst); err == nil { @@ -137,7 +144,7 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { return nil } - // Look for the file using smbclient cli + // If not mounted, try downloading the file using smbclient cli err := g.smbclientGetFile(req) if err == nil { return nil @@ -146,7 +153,7 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { err = fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) if !dstExisted { - // Remove the destination created for smbclient files + // Remove the destination created for smbclient if rerr := os.Remove(req.Dst); rerr != nil { err = fmt.Errorf("%s \n failed to remove created destination folder: %s", err.Error(), rerr.Error()) } @@ -160,8 +167,9 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { if err != nil { return err } - file := "" + // Get file and subdirectory name when existent + file := "" if strings.Contains(fileDir, "/") { i := strings.LastIndex(fileDir, "/") file = fileDir[i+1:] @@ -190,7 +198,7 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { return err } if isDir { - return fmt.Errorf("%s is a directory", file) + return fmt.Errorf("%s source path must be a file", file) } // download file @@ -244,6 +252,9 @@ func isDirectory(baseCmd string, file string) (bool, error) { if err != nil { return false, err } + if strings.Contains(output, "OBJECT_NAME_NOT_FOUND") { + return false, fmt.Errorf("source path not found: %s", output) + } return strings.Contains(output, "attributes: D"), nil } @@ -252,6 +263,9 @@ func runSmbClientCommand(cmd *exec.Cmd) (string, error) { cmd.Stdout = &buf cmd.Stderr = &buf err := cmd.Run() + if err == nil { + return buf.String(), nil + } if exiterr, ok := err.(*exec.ExitError); ok { // The program has exited with an exit code != 0 if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { @@ -262,5 +276,5 @@ func runSmbClientCommand(cmd *exec.Cmd) (string, error) { buf.String()) } } - return buf.String(), nil + return buf.String(), fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) } diff --git a/get_smb_test.go b/get_smb_test.go index afbd4eb60..12101a570 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -14,23 +14,22 @@ func TestSmbGetter_impl(t *testing.T) { } // TODO: -// tests: // allow download directory (?) // write higher level tests // save tests results on circleci - -type smbTest struct { - name string - rawURL string - file string - createFile string - fail bool -} +// write docs of how to run tests locally (makefile?) +// mount folder for testing func TestSmbGetter_Get(t *testing.T) { smbTestsPreCheck(t) - tests := []smbTest{ + tests := []struct{ + name string + rawURL string + file string + createFile string + fail bool + }{ { "smbclient with authentication", "smb://vagrant:vagrant@samba/shared/file.txt", @@ -160,7 +159,13 @@ func TestSmbGetter_Get(t *testing.T) { func TestSmbGetter_GetFile(t *testing.T) { smbTestsPreCheck(t) - tests := []smbTest{ + tests := []struct{ + name string + rawURL string + file string + createFile string + fail bool + }{ { "smbclient with authentication", "smb://vagrant:vagrant@samba/shared/file.txt", @@ -301,38 +306,69 @@ func TestSmbGetter_GetFile(t *testing.T) { } } +func TestSmbGetter_Mode(t *testing.T) { + smbTestsPreCheck(t) + + tests := []struct { + name string + rawURL string + expectedMode Mode + fail bool + }{ + { + "smbclient modefile for existing file", + "smb://vagrant:vagrant@samba/shared/file.txt", + ModeFile, + false, + }, + { + "smbclient modedir for existing directory", + "smb://vagrant:vagrant@samba/shared/subdir", + ModeDir, + false, + }, + { + "smbclient mode fail for unexisting directory", + "smb://vagrant:vagrant@samba/shared/invaliddir", + 0, + true, + }, + { + "smbclient mode fail for unexisting file", + "smb://vagrant:vagrant@samba/shared/invalidfile.txt", + 0, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + url, err := urlhelper.Parse(tt.rawURL) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + + g := new(SmbGetter) + mode, err := g.Mode(context.Background(), url) + + fail := err != nil + if tt.fail != fail { + if fail { + t.Fatalf("err: unexpected error %s", err.Error()) + } + t.Fatalf("err: expecting to fail but it did not") + } + + if mode != tt.expectedMode { + t.Fatalf("err: expeting mode %d, actual mode %d", tt.expectedMode, mode) + } + }) + } +} + func smbTestsPreCheck(t *testing.T) { r := os.Getenv("ACC_SMB_TEST") if r != "1" { t.Skip("Smb getter tests won't run. ACC_SMB_TEST not set") } } - -//func TestSmbGetter_Mode(t *testing.T) { -// g := new(SmbGetter) -// ctx := context.Background() -// -// // no hostname provided -// url, err := urlhelper.Parse("smb://") -// if err != nil { -// t.Fatalf("err: %s", err.Error()) -// } -// if _, err := g.Mode(ctx, url); err == nil { -// t.Fatalf("err: should fail when request url doesn't have a Host") -// } -// if _, err := g.Mode(ctx, url); err != nil && err.Error() != basePathError { -// t.Fatalf("err: expected error: %s\n but error was: %s", basePathError, err.Error()) -// } -// -// // no filepath provided -// url, err = urlhelper.Parse("smb://") -// if err != nil { -// t.Fatalf("err: %s", err.Error()) -// } -// if _, err := g.Mode(ctx, url); err == nil { -// t.Fatalf("err: should fail when request url doesn't have a Host") -// } -// if _, err := g.Mode(ctx, url); err != nil && err.Error() != basePathError { -// t.Fatalf("err: expected error: %s\n but error was: %s", basePathError, err.Error()) -// } -//} From 116f648a08a26dd1b4eb5592bfb71c96bbb6c4f8 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 14:58:33 +0200 Subject: [PATCH 041/109] fix fmt --- get_smb_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/get_smb_test.go b/get_smb_test.go index 12101a570..84485d642 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -23,7 +23,7 @@ func TestSmbGetter_impl(t *testing.T) { func TestSmbGetter_Get(t *testing.T) { smbTestsPreCheck(t) - tests := []struct{ + tests := []struct { name string rawURL string file string @@ -159,7 +159,7 @@ func TestSmbGetter_Get(t *testing.T) { func TestSmbGetter_GetFile(t *testing.T) { smbTestsPreCheck(t) - tests := []struct{ + tests := []struct { name string rawURL string file string @@ -310,10 +310,10 @@ func TestSmbGetter_Mode(t *testing.T) { smbTestsPreCheck(t) tests := []struct { - name string - rawURL string + name string + rawURL string expectedMode Mode - fail bool + fail bool }{ { "smbclient modefile for existing file", From 130a20e5d38b6df95ece2915ba5b593279d130df Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 15:50:34 +0200 Subject: [PATCH 042/109] add mounted folder as test --- .circleci/config.yml | 16 +++++-- docker-compose.yml | 3 ++ get_smb.go | 39 ++++++++-------- get_smb_test.go | 107 +++++++++++++++++++++++-------------------- go.mod | 1 + go.sum | 4 ++ 6 files changed, 99 insertions(+), 71 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4540bcf39..d1085f9cc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -79,11 +79,21 @@ jobs: command: sleep 60 - run: - name: run smb getter tests + name: prepare server and client to run tests command: | + docker exec -it samba bash -c "echo 'Hello' > data/file.txt && mkdir -p data/subdir && echo 'Hello' > data/subdir/file.txt" docker cp ./ gogetter:/go-getter/ - docker exec -it samba bash -c "touch data/file.txt && mkdir -p data/subdir && touch data/subdir/file.txt" - docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient && env ACC_SMB_TEST=1 go test -v ./... -run=TestSmbGetter_" + docker exec -it gogetter bash -c "go mod download \ + && apt-get update \ + && apt-get -y install smbclient \ + && apt-get -y install cifs-utils \ + && mkdir /mnt/shared \ + && mount -t cifs //samba/shared /mnt/shared -o user=vagrant,password=vagrant" + + - run: + name: run smb getter tests + command: | + docker exec -it gogetter bash -c "env ACC_SMB_TEST=1 go test -v ./... -run=TestSmbGetter_" workflows: version: 2 diff --git a/docker-compose.yml b/docker-compose.yml index 46aa08039..cfbcddda0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,6 +31,9 @@ services: - default working_dir: /go-getter command: tail -f /dev/null + cap_add: + - SYS_ADMIN + - DAC_READ_SEARCH networks: default: \ No newline at end of file diff --git a/get_smb.go b/get_smb.go index c74ff516d..363f5b114 100644 --- a/get_smb.go +++ b/get_smb.go @@ -11,6 +11,8 @@ import ( "runtime" "strings" "syscall" + + "github.com/hashicorp/go-multierror" ) // SmbGetter is a Getter implementation that will download a module from @@ -31,8 +33,9 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if runtime.GOOS == "windows" { path = "/" + path } - if m, err := mode(path); err == nil { - return m, nil + mode, result := mode(path) + if result == nil { + return mode, nil } // If not mounted, use smbclient cli to verify mode @@ -41,7 +44,8 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return mode, nil } - return 0, fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) + result = multierror.Append(result, err) + return 0, fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", result.Error()) } func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { @@ -60,7 +64,7 @@ func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { fileDir = "." } - getFileCmd := "smbclient -N" + baseCmd := "smbclient -N" // Append auth user and password to cmd auth := u.User.Username() @@ -68,12 +72,12 @@ func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { if password, ok := u.User.Password(); ok { auth = auth + "%" + password } - getFileCmd = getFileCmd + " -U " + auth + baseCmd = baseCmd + " -U " + auth } - baseCmd := getFileCmd + " " + hostPath + " --directory " + fileDir + baseCmd = baseCmd + " " + hostPath + " --directory " + fileDir - // check file exists in the smb shared folder to check the mode + // check if file exists in the smb shared folder and check the mode isDir, err := isDirectory(baseCmd, file) if err != nil { return 0, err @@ -140,7 +144,8 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { if runtime.GOOS == "windows" { path = "/" + path } - if err := getFile(path, req, ctx); err == nil { + result := getFile(path, req, ctx) + if result == nil { return nil } @@ -150,16 +155,14 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { return nil } - err = fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) + result = multierror.Append(result, err) if !dstExisted { // Remove the destination created for smbclient - if rerr := os.Remove(req.Dst); rerr != nil { - err = fmt.Errorf("%s \n failed to remove created destination folder: %s", err.Error(), rerr.Error()) - } + os.Remove(req.Dst) } - return err + return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", result.Error()) } func (g *SmbGetter) smbclientGetFile(req *Request) error { @@ -179,7 +182,7 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { fileDir = "." } - getFileCmd := "smbclient -N" + baseCmd := "smbclient -N" // Append auth user and password to cmd auth := req.u.User.Username() @@ -187,10 +190,10 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { if password, ok := req.u.User.Password(); ok { auth = auth + "%" + password } - getFileCmd = getFileCmd + " -U " + auth + baseCmd = baseCmd + " -U " + auth } - baseCmd := getFileCmd + " " + hostPath + " --directory " + fileDir + baseCmd = baseCmd + " " + hostPath + " --directory " + fileDir // check file exists in the smb shared folder and is not a directory isDir, err := isDirectory(baseCmd, file) @@ -202,8 +205,8 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { } // download file - getFileCmd = baseCmd + " --command " + fmt.Sprintf("'get %s'", file) - cmd := exec.Command("bash", "-c", getFileCmd) + baseCmd = baseCmd + " --command " + fmt.Sprintf("'get %s'", file) + cmd := exec.Command("bash", "-c", baseCmd) if req.Dst != "" { _, err := os.Lstat(req.Dst) diff --git a/get_smb_test.go b/get_smb_test.go index 84485d642..a76af8d65 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -18,7 +18,6 @@ func TestSmbGetter_impl(t *testing.T) { // write higher level tests // save tests results on circleci // write docs of how to run tests locally (makefile?) -// mount folder for testing func TestSmbGetter_Get(t *testing.T) { smbTestsPreCheck(t) @@ -160,102 +159,93 @@ func TestSmbGetter_GetFile(t *testing.T) { smbTestsPreCheck(t) tests := []struct { - name string - rawURL string - file string - createFile string - fail bool + name string + rawURL string + file string + mounted bool + fail bool }{ { "smbclient with authentication", "smb://vagrant:vagrant@samba/shared/file.txt", "file.txt", - "", + false, false, }, { "smbclient with authentication and subdirectory", "smb://vagrant:vagrant@samba/shared/subdir/file.txt", "file.txt", - "", + false, false, }, { "smbclient with only username authentication", "smb://vagrant@samba/shared/file.txt", "file.txt", - "", + false, false, }, { "smbclient without authentication", "smb://samba/shared/file.txt", "file.txt", - "", + false, false, }, { "smbclient get directory", "smb://vagrant:vagrant@samba/shared/subdir", "", - "", + false, + true, + }, + { + "local mounted smb shared file", + "smb://mnt/shared/file.txt", + "file.txt", true, + false, }, { - "smbclient get non existent file", - "smb://vagrant:vagrant@samba/shared/nonexistent.txt", + "local mounted smb shared directory", + "smb://mnt/shared/subdir", "", + true, + true, + }, + { + "non existent file", + "smb://vagrant:vagrant@samba/shared/invalidfile.txt", "", + false, true, }, { - "local mounted smb shared file", - "smb://samba/shared/mounted.txt", - "mounted.txt", - "/samba/shared/mounted.txt", + "non existent directory", + "smb://vagrant:vagrant@samba/shared/invaliddir", + "", false, + true, }, { "no hostname provided", "smb://", "", - "", + false, true, }, { "no filepath provided", "smb://samba", "", - "", + false, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.createFile != "" { - // mock mounted folder by creating one - err := os.MkdirAll(filepath.Dir(tt.createFile), 0755) - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - - f, err := os.Create(tt.createFile) - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - defer f.Close() - - // Write content to assert later - _, err = f.WriteString("Hello\n") - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - f.Sync() - - defer os.RemoveAll(tt.createFile) - } - dst := tempDir(t) defer os.RemoveAll(dst) @@ -280,24 +270,22 @@ func TestSmbGetter_GetFile(t *testing.T) { } if !tt.fail { - if tt.createFile != "" { - // Verify the destination folder is a symlink to mounted folder + if tt.mounted { + // Verify the destination folder is a symlink to the mounted one fi, err := os.Lstat(dst) if err != nil { - log.Printf("MOSS err 1") t.Fatalf("err: %s", err) } if fi.Mode()&os.ModeSymlink == 0 { t.Fatal("destination is not a symlink") } - // Verify the main file exists + // Verify the file exists assertContents(t, dst, "Hello\n") } else { - // Verify if the file was successfully download + // Verify if the file was successfully downloaded // and exists at the destination folder mainPath := filepath.Join(dst, tt.file) if _, err := os.Stat(mainPath); err != nil { - log.Printf("MOSS err 2") t.Fatalf("err: %s", err) } } @@ -313,6 +301,7 @@ func TestSmbGetter_Mode(t *testing.T) { name string rawURL string expectedMode Mode + mounted bool fail bool }{ { @@ -320,25 +309,43 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://vagrant:vagrant@samba/shared/file.txt", ModeFile, false, + false, }, { "smbclient modedir for existing directory", "smb://vagrant:vagrant@samba/shared/subdir", ModeDir, false, + false, }, { - "smbclient mode fail for unexisting directory", + "mode fail for non existent directory", "smb://vagrant:vagrant@samba/shared/invaliddir", 0, + false, true, }, { - "smbclient mode fail for unexisting file", + "mode fail for non existent file", "smb://vagrant:vagrant@samba/shared/invalidfile.txt", 0, + false, true, }, + { + "local mount modefile for existing file", + "smb://mnt/shared/file.txt", + ModeFile, + true, + false, + }, + { + "local mount modedir for existing directory", + "smb://mnt/shared/subdir", + ModeDir, + true, + false, + }, } for _, tt := range tests { diff --git a/go.mod b/go.mod index fdb259051..d9fd9db50 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/google/go-cmp v0.3.0 github.com/hashicorp/go-cleanhttp v0.5.0 github.com/hashicorp/go-getter v1.4.1 + github.com/hashicorp/go-multierror v1.1.0 github.com/hashicorp/go-safetemp v1.0.0 github.com/hashicorp/go-version v1.1.0 github.com/mitchellh/go-homedir v1.0.0 diff --git a/go.sum b/go.sum index 1f32e953d..4f8fab79b 100644 --- a/go.sum +++ b/go.sum @@ -42,10 +42,14 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-getter v1.4.1 h1:3A2Mh8smGFcf5M+gmcv898mZdrxpseik45IpcyISLsA= github.com/hashicorp/go-getter v1.4.1/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= From 76c6d38f25d924763e9bcbf1d4eb0bd06f4d9804 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 15:58:19 +0200 Subject: [PATCH 043/109] remove pass from mount command --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d1085f9cc..8384c8f41 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -88,7 +88,7 @@ jobs: && apt-get -y install smbclient \ && apt-get -y install cifs-utils \ && mkdir /mnt/shared \ - && mount -t cifs //samba/shared /mnt/shared -o user=vagrant,password=vagrant" + && mount -t cifs //samba/shared /mnt/shared -o user=,password=" - run: name: run smb getter tests From 5d602d5022cc2cf83c60dcfe8217d43fcdacc96b Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 16:09:30 +0200 Subject: [PATCH 044/109] change mount sec type --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8384c8f41..e5f6c2a5c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -88,7 +88,7 @@ jobs: && apt-get -y install smbclient \ && apt-get -y install cifs-utils \ && mkdir /mnt/shared \ - && mount -t cifs //samba/shared /mnt/shared -o user=,password=" + && mount -t cifs //samba/shared /mnt/shared -o user=,password=,sec=ntlm" - run: name: run smb getter tests From 625e7a14935489057807333c220ff8742f53cb72 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 16:12:29 +0200 Subject: [PATCH 045/109] change mount sec type --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e5f6c2a5c..9990e7f99 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -88,7 +88,7 @@ jobs: && apt-get -y install smbclient \ && apt-get -y install cifs-utils \ && mkdir /mnt/shared \ - && mount -t cifs //samba/shared /mnt/shared -o user=,password=,sec=ntlm" + && mount -t cifs //samba/shared /mnt/shared -o user=root,sec=none" - run: name: run smb getter tests From e4fade819b56b46bff29c24242b1ab270377cf55 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 16:45:40 +0200 Subject: [PATCH 046/109] Try cleaning docker-compose --- .circleci/config.yml | 2 +- docker-compose.yml | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9990e7f99..da2c4e9de 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -88,7 +88,7 @@ jobs: && apt-get -y install smbclient \ && apt-get -y install cifs-utils \ && mkdir /mnt/shared \ - && mount -t cifs //samba/shared /mnt/shared -o user=root,sec=none" + && mount -t cifs //samba/shared /mnt/shared -o user=user,password=password" - run: name: run smb getter tests diff --git a/docker-compose.yml b/docker-compose.yml index cfbcddda0..c999d3143 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,23 +4,16 @@ services: samba: image: dperson/samba container_name: samba - restart: unless-stopped environment: USERID: "0" GROUPID: "0" networks: - default ports: - - "137:137/udp" # required to advertise shares (NMBD) - - "138:138/udp" # required to advertise shares (NMBD) - "139:139/tcp" # default smb port - "445:445/tcp" # default smb port read_only: false - tmpfs: - - /tmp - volumes: - - /mnt:/mnt:z # :z allows share to be used by multiple containers - command: '-s "shared;/data"' + command: '-u "user;password" -s "shared;/data"' gogetter: image: golang:latest From a1c9f214aa3034430f5db6bd5bcbd3e4887cb0e2 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 16:49:31 +0200 Subject: [PATCH 047/109] Remove local mount tests and config --- .circleci/config.yml | 5 +--- get_smb_test.go | 56 ++++++++++++++++++++++---------------------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index da2c4e9de..8af5882c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,10 +85,7 @@ jobs: docker cp ./ gogetter:/go-getter/ docker exec -it gogetter bash -c "go mod download \ && apt-get update \ - && apt-get -y install smbclient \ - && apt-get -y install cifs-utils \ - && mkdir /mnt/shared \ - && mount -t cifs //samba/shared /mnt/shared -o user=user,password=password" + && apt-get -y install smbclient" - run: name: run smb getter tests diff --git a/get_smb_test.go b/get_smb_test.go index a76af8d65..4097fd085 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -200,20 +200,20 @@ func TestSmbGetter_GetFile(t *testing.T) { false, true, }, - { - "local mounted smb shared file", - "smb://mnt/shared/file.txt", - "file.txt", - true, - false, - }, - { - "local mounted smb shared directory", - "smb://mnt/shared/subdir", - "", - true, - true, - }, + //{ + // "local mounted smb shared file", + // "smb://mnt/shared/file.txt", + // "file.txt", + // true, + // false, + //}, + //{ + // "local mounted smb shared directory", + // "smb://mnt/shared/subdir", + // "", + // true, + // true, + //}, { "non existent file", "smb://vagrant:vagrant@samba/shared/invalidfile.txt", @@ -332,20 +332,20 @@ func TestSmbGetter_Mode(t *testing.T) { false, true, }, - { - "local mount modefile for existing file", - "smb://mnt/shared/file.txt", - ModeFile, - true, - false, - }, - { - "local mount modedir for existing directory", - "smb://mnt/shared/subdir", - ModeDir, - true, - false, - }, + //{ + // "local mount modefile for existing file", + // "smb://mnt/shared/file.txt", + // ModeFile, + // true, + // false, + //}, + //{ + // "local mount modedir for existing directory", + // "smb://mnt/shared/subdir", + // ModeDir, + // true, + // false, + //}, } for _, tt := range tests { From b42688500035ac85492c8f075f295c23b74dd24f Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 16:56:21 +0200 Subject: [PATCH 048/109] try back mounting samba folder --- .circleci/config.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8af5882c5..111c3b4fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,7 +85,10 @@ jobs: docker cp ./ gogetter:/go-getter/ docker exec -it gogetter bash -c "go mod download \ && apt-get update \ - && apt-get -y install smbclient" + && apt-get -y install smbclient \ + && apt-get -y install cifs-utils \ + && mkdir /mnt/shared \ + && mount -t cifs -o user=user,password=password,sec=ntlmssp //samba/shared /mnt/shared " - run: name: run smb getter tests From d3bdbe71bf2ba00641355e9cb0ddf2bf4a495650 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 20 Apr 2020 18:20:58 +0200 Subject: [PATCH 049/109] remove mount --- .circleci/config.yml | 9 ++-- docker-compose.yml | 3 -- get_smb_test.go | 110 +++++++++++++++++++++++++++++-------------- 3 files changed, 78 insertions(+), 44 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 111c3b4fb..80ac8cdde 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -81,14 +81,13 @@ jobs: - run: name: prepare server and client to run tests command: | - docker exec -it samba bash -c "echo 'Hello' > data/file.txt && mkdir -p data/subdir && echo 'Hello' > data/subdir/file.txt" + docker exec -it samba bash -c "echo 'Hello' > data/file.txt \ + && mkdir -p data/subdir \ + && echo 'Hello' > data/subdir/file.txt" docker cp ./ gogetter:/go-getter/ docker exec -it gogetter bash -c "go mod download \ && apt-get update \ - && apt-get -y install smbclient \ - && apt-get -y install cifs-utils \ - && mkdir /mnt/shared \ - && mount -t cifs -o user=user,password=password,sec=ntlmssp //samba/shared /mnt/shared " + && apt-get -y install smbclient" - run: name: run smb getter tests diff --git a/docker-compose.yml b/docker-compose.yml index c999d3143..8b24f669c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,9 +24,6 @@ services: - default working_dir: /go-getter command: tail -f /dev/null - cap_add: - - SYS_ADMIN - - DAC_READ_SEARCH networks: default: \ No newline at end of file diff --git a/get_smb_test.go b/get_smb_test.go index 4097fd085..621e4ed2c 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -159,93 +159,116 @@ func TestSmbGetter_GetFile(t *testing.T) { smbTestsPreCheck(t) tests := []struct { - name string - rawURL string - file string - mounted bool - fail bool + name string + rawURL string + file string + createFile string + fail bool }{ { "smbclient with authentication", "smb://vagrant:vagrant@samba/shared/file.txt", "file.txt", - false, + "", false, }, { "smbclient with authentication and subdirectory", "smb://vagrant:vagrant@samba/shared/subdir/file.txt", "file.txt", - false, + "", false, }, { "smbclient with only username authentication", "smb://vagrant@samba/shared/file.txt", "file.txt", - false, + "", false, }, { "smbclient without authentication", "smb://samba/shared/file.txt", "file.txt", - false, + "", false, }, { "smbclient get directory", "smb://vagrant:vagrant@samba/shared/subdir", "", - false, + "", true, }, - //{ - // "local mounted smb shared file", - // "smb://mnt/shared/file.txt", - // "file.txt", - // true, - // false, - //}, + { + "local mounted smb shared file", + "smb://mnt/shared/file.txt", + "file.txt", + "/mnt/shared/file.txt", + false, + }, //{ // "local mounted smb shared directory", // "smb://mnt/shared/subdir", // "", - // true, + // "//mnt/shared/subdir", // true, //}, { "non existent file", "smb://vagrant:vagrant@samba/shared/invalidfile.txt", "", - false, + "", true, }, { "non existent directory", "smb://vagrant:vagrant@samba/shared/invaliddir", "", - false, + "", true, }, { "no hostname provided", "smb://", "", - false, + "", true, }, { "no filepath provided", "smb://samba", "", - false, + "", true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + if tt.createFile != "" { + // mock mounted folder by creating one + err := os.MkdirAll(filepath.Dir(tt.createFile), 0755) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + + f, err := os.Create(tt.createFile) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + defer f.Close() + + // Write content to assert later + _, err = f.WriteString("Hello\n") + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + f.Sync() + + defer os.RemoveAll(tt.createFile) + } + dst := tempDir(t) defer os.RemoveAll(dst) @@ -270,7 +293,7 @@ func TestSmbGetter_GetFile(t *testing.T) { } if !tt.fail { - if tt.mounted { + if tt.createFile != "" { // Verify the destination folder is a symlink to the mounted one fi, err := os.Lstat(dst) if err != nil { @@ -301,55 +324,70 @@ func TestSmbGetter_Mode(t *testing.T) { name string rawURL string expectedMode Mode - mounted bool + createFile string fail bool }{ { "smbclient modefile for existing file", "smb://vagrant:vagrant@samba/shared/file.txt", ModeFile, - false, + "", false, }, { "smbclient modedir for existing directory", "smb://vagrant:vagrant@samba/shared/subdir", ModeDir, - false, + "", false, }, { "mode fail for non existent directory", "smb://vagrant:vagrant@samba/shared/invaliddir", 0, - false, + "", true, }, { "mode fail for non existent file", "smb://vagrant:vagrant@samba/shared/invalidfile.txt", 0, - false, + "", true, }, - //{ - // "local mount modefile for existing file", - // "smb://mnt/shared/file.txt", - // ModeFile, - // true, - // false, - //}, + { + "local mount modefile for existing file", + "smb://mnt/shared/file.txt", + ModeFile, + "/mnt/shared/file.txt", + false, + }, //{ // "local mount modedir for existing directory", // "smb://mnt/shared/subdir", // ModeDir, - // true, + // "/mnt/shared/subdir", // false, //}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + if tt.createFile != "" { + // mock mounted folder by creating one + err := os.MkdirAll(filepath.Dir(tt.createFile), 0755) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + + _, err = os.Create(tt.createFile) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + + //defer os.RemoveAll(tt.createFile) + } + url, err := urlhelper.Parse(tt.rawURL) if err != nil { t.Fatalf("err: %s", err.Error()) From b2674f8ff8558c30bd2580468db5b94ea792c460 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 21 Apr 2020 11:19:30 +0200 Subject: [PATCH 050/109] add makefile and write tests --- Makefile | 11 ++++++ get_smb_test.go | 98 +++++++++++++++++++++++++++---------------------- 2 files changed, 66 insertions(+), 43 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..040fdf207 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +smbtests-prepare: + @docker-compose up -d + @sleep 60 + @docker cp ./ gogetter:/go-getter/ + @docker exec -it samba bash -c "echo 'Hello' > data/file.txt && mkdir -p data/subdir && echo 'Hello' > data/subdir/file.txt" + @docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient" + + +smbtests: + @docker cp ./ gogetter:/go-getter/ + @docker exec -it gogetter bash -c "env ACC_SMB_TEST=1 go test -v ./... -run=TestSmbGetter_" \ No newline at end of file diff --git a/get_smb_test.go b/get_smb_test.go index 621e4ed2c..11fdde3c2 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -159,11 +159,11 @@ func TestSmbGetter_GetFile(t *testing.T) { smbTestsPreCheck(t) tests := []struct { - name string - rawURL string - file string - createFile string - fail bool + name string + rawURL string + file string + createDir string + fail bool }{ { "smbclient with authentication", @@ -204,16 +204,16 @@ func TestSmbGetter_GetFile(t *testing.T) { "local mounted smb shared file", "smb://mnt/shared/file.txt", "file.txt", - "/mnt/shared/file.txt", + "/mnt/shared", false, }, - //{ - // "local mounted smb shared directory", - // "smb://mnt/shared/subdir", - // "", - // "//mnt/shared/subdir", - // true, - //}, + { + "local mounted smb shared directory", + "smb://mnt/shared/subdir", + "", + "/mnt/shared/subdir", + true, + }, { "non existent file", "smb://vagrant:vagrant@samba/shared/invalidfile.txt", @@ -246,27 +246,29 @@ func TestSmbGetter_GetFile(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.createFile != "" { + if tt.createDir != "" { // mock mounted folder by creating one - err := os.MkdirAll(filepath.Dir(tt.createFile), 0755) + err := os.MkdirAll(tt.createDir, 0755) if err != nil { t.Fatalf("err: %s", err.Error()) } - f, err := os.Create(tt.createFile) - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - defer f.Close() + if tt.file != "" { + f, err := os.Create(filepath.Join(tt.createDir, tt.file)) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + defer f.Close() - // Write content to assert later - _, err = f.WriteString("Hello\n") - if err != nil { - t.Fatalf("err: %s", err.Error()) + // Write content to assert later + _, err = f.WriteString("Hello\n") + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + f.Sync() } - f.Sync() - defer os.RemoveAll(tt.createFile) + defer os.RemoveAll(tt.createDir) } dst := tempDir(t) @@ -293,7 +295,7 @@ func TestSmbGetter_GetFile(t *testing.T) { } if !tt.fail { - if tt.createFile != "" { + if tt.createDir != "" { // Verify the destination folder is a symlink to the mounted one fi, err := os.Lstat(dst) if err != nil { @@ -324,13 +326,15 @@ func TestSmbGetter_Mode(t *testing.T) { name string rawURL string expectedMode Mode - createFile string + file string + createDir string fail bool }{ { "smbclient modefile for existing file", "smb://vagrant:vagrant@samba/shared/file.txt", ModeFile, + "file.txt", "", false, }, @@ -339,6 +343,7 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://vagrant:vagrant@samba/shared/subdir", ModeDir, "", + "", false, }, { @@ -346,6 +351,7 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://vagrant:vagrant@samba/shared/invaliddir", 0, "", + "", true, }, { @@ -353,39 +359,45 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://vagrant:vagrant@samba/shared/invalidfile.txt", 0, "", + "", true, }, { "local mount modefile for existing file", "smb://mnt/shared/file.txt", ModeFile, - "/mnt/shared/file.txt", + "file.txt", + "/mnt/shared", + false, + }, + { + "local mount modedir for existing directory", + "smb://mnt/shared/subdir", + ModeDir, + "", + "/mnt/shared/subdir", false, }, - //{ - // "local mount modedir for existing directory", - // "smb://mnt/shared/subdir", - // ModeDir, - // "/mnt/shared/subdir", - // false, - //}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.createFile != "" { + if tt.createDir != "" { // mock mounted folder by creating one - err := os.MkdirAll(filepath.Dir(tt.createFile), 0755) + err := os.MkdirAll(tt.createDir, 0755) if err != nil { t.Fatalf("err: %s", err.Error()) } - _, err = os.Create(tt.createFile) - if err != nil { - t.Fatalf("err: %s", err.Error()) + if tt.file != "" { + f, err := os.Create(filepath.Join(tt.createDir, tt.file)) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + defer f.Close() } - //defer os.RemoveAll(tt.createFile) + defer os.RemoveAll(tt.createDir) } url, err := urlhelper.Parse(tt.rawURL) @@ -414,6 +426,6 @@ func TestSmbGetter_Mode(t *testing.T) { func smbTestsPreCheck(t *testing.T) { r := os.Getenv("ACC_SMB_TEST") if r != "1" { - t.Skip("Smb getter tests won't run. ACC_SMB_TEST not set") + t.Skip("smb getter tests won't run. ACC_SMB_TEST not set") } } From 171bb461e47ff1cb7b856652af3e0ccd1d231cde Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 21 Apr 2020 14:27:35 +0200 Subject: [PATCH 051/109] download dir with smbclient --- get_file.go | 12 ++-- get_smb.go | 168 +++++++++++++++++++++++++++--------------------- get_smb_test.go | 106 ++++++++++++++++-------------- 3 files changed, 155 insertions(+), 131 deletions(-) diff --git a/get_file.go b/get_file.go index ed242f111..48859860b 100644 --- a/get_file.go +++ b/get_file.go @@ -19,10 +19,10 @@ func (g *FileGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.RawPath != "" { path = u.RawPath } - return mode(path) + return g.mode(path) } -func mode(path string) (Mode, error) { +func (g *FileGetter) mode(path string) (Mode, error) { fi, err := os.Stat(path) if err != nil { return 0, err @@ -41,10 +41,10 @@ func (g *FileGetter) Get(ctx context.Context, req *Request) error { if req.u.RawPath != "" { path = req.u.RawPath } - return get(path, req) + return g.get(path, req) } -func get(path string, req *Request) error { +func (g *FileGetter) get(path string, req *Request) error { // The source path must exist and be a directory to be usable. if fi, err := os.Stat(path); err != nil { return fmt.Errorf("source path error: %s", err) @@ -88,10 +88,10 @@ func (g *FileGetter) GetFile(ctx context.Context, req *Request) error { if req.u.RawPath != "" { path = req.u.RawPath } - return getFile(path, req, ctx) + return g.getFile(path, req, ctx) } -func getFile(path string, req *Request, ctx context.Context) error { +func (g *FileGetter) getFile(path string, req *Request, ctx context.Context) error { // The source path must exist and be a file to be usable. if fi, err := os.Stat(path); err != nil { return fmt.Errorf("source path error: %s", err) diff --git a/get_smb.go b/get_smb.go index 363f5b114..64d4bb626 100644 --- a/get_smb.go +++ b/get_smb.go @@ -16,7 +16,7 @@ import ( ) // SmbGetter is a Getter implementation that will download a module from -// a shared folder using samba scheme. +// a shared folder using smbclient cli or looking for local mount. type SmbGetter struct { getter } @@ -33,7 +33,8 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if runtime.GOOS == "windows" { path = "/" + path } - mode, result := mode(path) + f := new(FileGetter) + mode, result := f.mode(path) if result == nil { return mode, nil } @@ -49,34 +50,22 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { } func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { - hostPath, fileDir, err := g.findHostAndFilePath(u) + hostPath, filePath, err := findHostAndFilePath(u) if err != nil { return 0, err } file := "" // Get file and subdirectory name when existent - if strings.Contains(fileDir, "/") { - i := strings.LastIndex(fileDir, "/") - file = fileDir[i+1:] - fileDir = fileDir[:i] + if strings.Contains(filePath, "/") { + i := strings.LastIndex(filePath, "/") + file = filePath[i+1:] + filePath = filePath[:i] } else { - file = fileDir - fileDir = "." + file = filePath + filePath = "." } - baseCmd := "smbclient -N" - - // Append auth user and password to cmd - auth := u.User.Username() - if auth != "" { - if password, ok := u.User.Password(); ok { - auth = auth + "%" + password - } - baseCmd = baseCmd + " -U " + auth - } - - baseCmd = baseCmd + " " + hostPath + " --directory " + fileDir - + baseCmd := smbclientBaseCmd(u.User, hostPath, filePath) // check if file exists in the smb shared folder and check the mode isDir, err := isDirectory(baseCmd, file) if err != nil { @@ -88,11 +77,12 @@ func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { return ModeFile, nil } -// TODO: copy directory func (g *SmbGetter) Get(ctx context.Context, req *Request) error { if req.u.Host == "" || req.u.Path == "" { return fmt.Errorf(basePathError) } + + // If dst folder doesn't exists, we need to remove the created on later in case of failures dstExisted := false if req.Dst != "" { if _, err := os.Lstat(req.Dst); err == nil { @@ -100,30 +90,52 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { } } - // First look in a possible local mount of the shared folder + // First look in a possible local mount of the shared folder path := "/" + req.u.Host + req.u.Path if runtime.GOOS == "windows" { path = "/" + path } - if err := get(path, req); err == nil { + f := new(FileGetter) + result := f.get(path, req) + if result == nil { return nil } - // Look for the file using smbclient cli - err := g.smbclientGetFile(req) + // If not mounted, try downloading the directory content using smbclient cli + err := g.smbclientGet(req) if err == nil { return nil } - err = fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", err.Error()) + result = multierror.Append(result, err) if !dstExisted { - // Remove the destination created for smbclient files - if rerr := os.Remove(req.Dst); rerr != nil { - err = fmt.Errorf("%s \n failed to remove created destination folder: %s", err.Error(), rerr.Error()) - } + // Remove the destination created for smbclient + os.Remove(req.Dst) } + return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", result.Error()) +} + +func (g *SmbGetter) smbclientGet(req *Request) error { + hostPath, directory, err := findHostAndFilePath(req.u) + if err != nil { + return err + } + + baseCmd := smbclientBaseCmd(req.u.User, hostPath, ".") + // check directory exists in the smb shared folder and is a directory + isDir, err := isDirectory(baseCmd, directory) + if err != nil { + return err + } + if !isDir { + return fmt.Errorf("%s source path must be a directory", directory) + } + + // download everything that's inside the directory (files and subdirectories) + smbclientCmd := baseCmd + " --command 'prompt OFF;recurse ON; mget *'" + _, err = runSmbClientCommand(smbclientCmd, req.Dst) return err } @@ -132,6 +144,7 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { return fmt.Errorf(basePathError) } + // If dst folder doesn't exists, we need to remove the created on later in case of failures dstExisted := false if req.Dst != "" { if _, err := os.Lstat(req.Dst); err == nil { @@ -144,7 +157,8 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { if runtime.GOOS == "windows" { path = "/" + path } - result := getFile(path, req, ctx) + f := new(FileGetter) + result := f.getFile(path, req, ctx) if result == nil { return nil } @@ -166,35 +180,23 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { } func (g *SmbGetter) smbclientGetFile(req *Request) error { - hostPath, fileDir, err := g.findHostAndFilePath(req.u) + hostPath, filePath, err := findHostAndFilePath(req.u) if err != nil { return err } // Get file and subdirectory name when existent file := "" - if strings.Contains(fileDir, "/") { - i := strings.LastIndex(fileDir, "/") - file = fileDir[i+1:] - fileDir = fileDir[:i] + if strings.Contains(filePath, "/") { + i := strings.LastIndex(filePath, "/") + file = filePath[i+1:] + filePath = filePath[:i] } else { - file = fileDir - fileDir = "." + file = filePath + filePath = "." } - baseCmd := "smbclient -N" - - // Append auth user and password to cmd - auth := req.u.User.Username() - if auth != "" { - if password, ok := req.u.User.Password(); ok { - auth = auth + "%" + password - } - baseCmd = baseCmd + " -U " + auth - } - - baseCmd = baseCmd + " " + hostPath + " --directory " + fileDir - + baseCmd := smbclientBaseCmd(req.u.User, hostPath, filePath) // check file exists in the smb shared folder and is not a directory isDir, err := isDirectory(baseCmd, file) if err != nil { @@ -205,29 +207,28 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { } // download file - baseCmd = baseCmd + " --command " + fmt.Sprintf("'get %s'", file) - cmd := exec.Command("bash", "-c", baseCmd) + smbclientCmd := baseCmd + " --command " + fmt.Sprintf("'get %s'", file) + _, err = runSmbClientCommand(smbclientCmd, req.Dst) + return err +} - if req.Dst != "" { - _, err := os.Lstat(req.Dst) - if err != nil { - if os.IsNotExist(err) { - // Create destination folder if it doesn't exists - if err := os.MkdirAll(req.Dst, os.ModePerm); err != nil { - return fmt.Errorf("failed to creat destination path: %s", err.Error()) - } - } else { - return err - } +func smbclientBaseCmd(used *url.Userinfo, hostPath string, fileDir string) string { + baseCmd := "smbclient -N" + + // Append auth user and password to baseCmd + auth := used.Username() + if auth != "" { + if password, ok := used.Password(); ok { + auth = auth + "%" + password } - cmd.Dir = req.Dst + baseCmd = baseCmd + " -U " + auth } - _, err = runSmbClientCommand(cmd) - return err + baseCmd = baseCmd + " " + hostPath + " --directory " + fileDir + return baseCmd } -func (g *SmbGetter) findHostAndFilePath(u *url.URL) (string, string, error) { +func findHostAndFilePath(u *url.URL) (string, string, error) { // Host path hostPath := "//" + u.Host @@ -248,10 +249,9 @@ func (g *SmbGetter) findHostAndFilePath(u *url.URL) (string, string, error) { return hostPath, directories[1], nil } -func isDirectory(baseCmd string, file string) (bool, error) { - fileInfoCmd := baseCmd + " --command " + fmt.Sprintf("'allinfo %s'", file) - cmd := exec.Command("bash", "-c", fileInfoCmd) - output, err := runSmbClientCommand(cmd) +func isDirectory(baseCmd string, object string) (bool, error) { + objectInfoCmd := baseCmd + " --command " + fmt.Sprintf("'allinfo %s'", object) + output, err := runSmbClientCommand(objectInfoCmd, "") if err != nil { return false, err } @@ -261,10 +261,28 @@ func isDirectory(baseCmd string, file string) (bool, error) { return strings.Contains(output, "attributes: D"), nil } -func runSmbClientCommand(cmd *exec.Cmd) (string, error) { +func runSmbClientCommand(smbclientCmd string, dst string) (string, error) { + cmd := exec.Command("bash", "-c", smbclientCmd) + + if dst != "" { + _, err := os.Lstat(dst) + if err != nil { + if os.IsNotExist(err) { + // Create destination folder if it doesn't exists + if err := os.MkdirAll(dst, os.ModePerm); err != nil { + return "", fmt.Errorf("failed to creat destination path: %s", err.Error()) + } + } else { + return "", err + } + } + cmd.Dir = dst + } + var buf bytes.Buffer cmd.Stdout = &buf cmd.Stderr = &buf + err := cmd.Run() if err == nil { return buf.String(), nil diff --git a/get_smb_test.go b/get_smb_test.go index 11fdde3c2..a858d02c5 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -3,7 +3,6 @@ package getter import ( "context" urlhelper "github.com/hashicorp/go-getter/helper/url" - "log" "os" "path/filepath" "testing" @@ -14,54 +13,61 @@ func TestSmbGetter_impl(t *testing.T) { } // TODO: -// allow download directory (?) // write higher level tests // save tests results on circleci // write docs of how to run tests locally (makefile?) +// update readme func TestSmbGetter_Get(t *testing.T) { smbTestsPreCheck(t) tests := []struct { - name string - rawURL string - file string - createFile string - fail bool + name string + rawURL string + file string + createDir string + fail bool }{ { "smbclient with authentication", - "smb://vagrant:vagrant@samba/shared/file.txt", + "smb://username:password@samba/shared/subdir", "file.txt", "", false, }, { - "smbclient with authentication and subdir", - "smb://vagrant:vagrant@samba/shared/subdir/file.txt", + "smbclient with authentication with file", + "smb://username:password@samba/shared/subdir/file.txt", "file.txt", "", - false, + true, }, { "smbclient with only username authentication", - "smb://vagrant@samba/shared/file.txt", + "smb://username@samba/shared/subdir", "file.txt", "", false, }, { "smbclient without authentication", - "smb://samba/shared/file.txt", + "smb://samba/shared/subdir", "file.txt", "", false, }, { "local mounted smb shared file", - "smb://samba/shared/mounted.txt", - "mounted.txt", - "/samba/shared/mounted.txt", + "smb://mnt/shared/file.txt", + "file.txt", + "/mnt/shared", + true, + }, + { + "local mounted smb shared directory", + "smb://mnt/shared/subdir", + "file.txt", + "/mnt/shared/subdir", false, }, { @@ -82,27 +88,29 @@ func TestSmbGetter_Get(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.createFile != "" { + if tt.createDir != "" { // mock mounted folder by creating one - err := os.MkdirAll(filepath.Dir(tt.createFile), 0755) + err := os.MkdirAll(tt.createDir, 0755) if err != nil { t.Fatalf("err: %s", err.Error()) } - f, err := os.Create(tt.createFile) - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - defer f.Close() + if tt.file != "" { + f, err := os.Create(filepath.Join(tt.createDir, tt.file)) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + defer f.Close() - // Write content to assert later - _, err = f.WriteString("Hello\n") - if err != nil { - t.Fatalf("err: %s", err.Error()) + // Write content to assert later + _, err = f.WriteString("Hello\n") + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + f.Sync() } - f.Sync() - defer os.RemoveAll(tt.createFile) + defer os.RemoveAll(tt.createDir) } dst := tempDir(t) @@ -118,10 +126,9 @@ func TestSmbGetter_Get(t *testing.T) { } g := new(SmbGetter) - ctx := context.Background() - err = g.GetFile(ctx, req) - fail := err != nil + err = g.Get(context.Background(), req) + fail := err != nil if tt.fail != fail { if fail { t.Fatalf("err: unexpected error %s", err.Error()) @@ -130,23 +137,22 @@ func TestSmbGetter_Get(t *testing.T) { } if !tt.fail { - if tt.createFile != "" { - // Verify the destination folder is a symlink to mounted folder + if tt.createDir != "" { + // Verify the destination folder is a symlink to the mounted one fi, err := os.Lstat(dst) if err != nil { - log.Printf("MOSS err 1") t.Fatalf("err: %s", err) } if fi.Mode()&os.ModeSymlink == 0 { t.Fatal("destination is not a symlink") } - // Verify the main file exists - assertContents(t, dst, "Hello\n") + // Verify the file exists + assertContents(t, filepath.Join(dst, tt.file), "Hello\n") } else { - // Verify the file exists at the destination folder + // Verify if the file was successfully downloaded + // and exists at the destination folder mainPath := filepath.Join(dst, tt.file) if _, err := os.Stat(mainPath); err != nil { - log.Printf("MOSS err 2") t.Fatalf("err: %s", err) } } @@ -167,21 +173,21 @@ func TestSmbGetter_GetFile(t *testing.T) { }{ { "smbclient with authentication", - "smb://vagrant:vagrant@samba/shared/file.txt", + "smb://username:password@samba/shared/file.txt", "file.txt", "", false, }, { "smbclient with authentication and subdirectory", - "smb://vagrant:vagrant@samba/shared/subdir/file.txt", + "smb://username:password@samba/shared/subdir/file.txt", "file.txt", "", false, }, { "smbclient with only username authentication", - "smb://vagrant@samba/shared/file.txt", + "smb://username@samba/shared/file.txt", "file.txt", "", false, @@ -195,7 +201,7 @@ func TestSmbGetter_GetFile(t *testing.T) { }, { "smbclient get directory", - "smb://vagrant:vagrant@samba/shared/subdir", + "smb://username:password@samba/shared/subdir", "", "", true, @@ -216,14 +222,14 @@ func TestSmbGetter_GetFile(t *testing.T) { }, { "non existent file", - "smb://vagrant:vagrant@samba/shared/invalidfile.txt", + "smb://username:password@samba/shared/invalidfile.txt", "", "", true, }, { "non existent directory", - "smb://vagrant:vagrant@samba/shared/invaliddir", + "smb://username:password@samba/shared/invaliddir", "", "", true, @@ -326,13 +332,13 @@ func TestSmbGetter_Mode(t *testing.T) { name string rawURL string expectedMode Mode - file string + file string createDir string fail bool }{ { "smbclient modefile for existing file", - "smb://vagrant:vagrant@samba/shared/file.txt", + "smb://username:password@samba/shared/file.txt", ModeFile, "file.txt", "", @@ -340,7 +346,7 @@ func TestSmbGetter_Mode(t *testing.T) { }, { "smbclient modedir for existing directory", - "smb://vagrant:vagrant@samba/shared/subdir", + "smb://username:password@samba/shared/subdir", ModeDir, "", "", @@ -348,7 +354,7 @@ func TestSmbGetter_Mode(t *testing.T) { }, { "mode fail for non existent directory", - "smb://vagrant:vagrant@samba/shared/invaliddir", + "smb://username:password@samba/shared/invaliddir", 0, "", "", @@ -356,7 +362,7 @@ func TestSmbGetter_Mode(t *testing.T) { }, { "mode fail for non existent file", - "smb://vagrant:vagrant@samba/shared/invalidfile.txt", + "smb://username:password@samba/shared/invalidfile.txt", 0, "", "", From 4e79911a3fafa315642e4c6eb89b0f8908d55fb3 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 21 Apr 2020 15:11:02 +0200 Subject: [PATCH 052/109] update readme --- Makefile | 3 +++ README.md | 21 +++++++++++++++++++++ get_smb_test.go | 17 +++++++---------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 040fdf207..b63f2c0b1 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +start-smb: + docker-compose up -d samba + smbtests-prepare: @docker-compose up -d @sleep 60 diff --git a/README.md b/README.md index 64f1bafff..341776629 100644 --- a/README.md +++ b/README.md @@ -361,3 +361,24 @@ In order to access to GCS, authentication credentials should be provided. More i #### GCS Testing The tests for `get_gcs.go` require you to have GCP credentials set in your environment. These credentials can have any level of permissions to any project, they just need to exist. This means setting `GOOGLE_APPLICATION_CREDENTIALS="~/path/to/credentials.json"` or `GOOGLE_CREDENTIALS="{stringified-credentials-json}"`. Due to this configuration, `get_gcs_test.go` will fail for external contributors in CircleCI. + +### SMB (smb) + +To access samba shared folder it's necessary to have a [smbclient](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) installed or a local mount of the shared folder. + +Some examples for addressing the scheme: +- smb://username:password@host/shared/dir (downloads directory content) +- smb://username@host/shared/dir +- smb://host/shared/dir +- smb://username:password@host/shared/dir/file (downloads file) +- smb://username@host/shared/dir/file +- smb://host/shared/dir/file + +#### SMB Testing +The test for `get_smb.go` requires a smb server running which can be started inside a docker container by +running `make start-smb`. Once the container is up the shared folder can be accessed via `smb:///shared/` +by another container or machine in the same network. + +To run the tests inside `get_smb_test.go`, prepare the environment with `make smbtests-prepare`. On prepare some +mock files and directories will be added to the shared folder and a go-getter container will start together with the samba server. +Once the environment for testing is prepared, run `make smbtests` to run the tests. \ No newline at end of file diff --git a/get_smb_test.go b/get_smb_test.go index a858d02c5..d40aa9e47 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -13,10 +13,7 @@ func TestSmbGetter_impl(t *testing.T) { } // TODO: -// write higher level tests // save tests results on circleci -// write docs of how to run tests locally (makefile?) -// update readme func TestSmbGetter_Get(t *testing.T) { smbTestsPreCheck(t) @@ -70,6 +67,13 @@ func TestSmbGetter_Get(t *testing.T) { "/mnt/shared/subdir", false, }, + { + "non existent directory", + "smb://username:password@samba/shared/invalid", + "", + "", + true, + }, { "no hostname provided", "smb://", @@ -227,13 +231,6 @@ func TestSmbGetter_GetFile(t *testing.T) { "", true, }, - { - "non existent directory", - "smb://username:password@samba/shared/invaliddir", - "", - "", - true, - }, { "no hostname provided", "smb://", From 42954fffa7474c9b8ef806b6f95ee0b94ef8158c Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 21 Apr 2020 16:20:44 +0200 Subject: [PATCH 053/109] write better ests names and update makefile --- Makefile | 2 +- docker-compose.yml | 3 +- get_smb_test.go | 85 ++++++++++++++++++++++++++-------------------- 3 files changed, 50 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index b63f2c0b1..a9b4b5759 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ smbtests-prepare: @docker-compose up -d @sleep 60 @docker cp ./ gogetter:/go-getter/ - @docker exec -it samba bash -c "echo 'Hello' > data/file.txt && mkdir -p data/subdir && echo 'Hello' > data/subdir/file.txt" @docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient" + @docker exec -it samba bash -c "echo 'Hello' > data/file.txt && mkdir -p data/subdir && echo 'Hello' > data/subdir/file.txt" smbtests: diff --git a/docker-compose.yml b/docker-compose.yml index 8b24f669c..930d0c167 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,8 +12,7 @@ services: ports: - "139:139/tcp" # default smb port - "445:445/tcp" # default smb port - read_only: false - command: '-u "user;password" -s "shared;/data"' + command: '-u "user;password" -s "public;/data"' gogetter: image: golang:latest diff --git a/get_smb_test.go b/get_smb_test.go index d40aa9e47..346f103b5 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -12,9 +12,6 @@ func TestSmbGetter_impl(t *testing.T) { var _ Getter = new(SmbGetter) } -// TODO: -// save tests results on circleci - func TestSmbGetter_Get(t *testing.T) { smbTestsPreCheck(t) @@ -26,50 +23,57 @@ func TestSmbGetter_Get(t *testing.T) { fail bool }{ { - "smbclient with authentication", - "smb://username:password@samba/shared/subdir", + "smbclient with registered authentication in public share", + "smb://user:password@samba/public/subdir", "file.txt", "", false, }, { - "smbclient with authentication with file", - "smb://username:password@samba/shared/subdir/file.txt", + "smbclient with registered authentication with file in public share", + "smb://user:password@samba/public/subdir/file.txt", + "file.txt", + "", + true, + }, + { + "smbclient with only registered username authentication in public share", + "smb://user@samba/public/subdir", "file.txt", "", true, }, { - "smbclient with only username authentication", - "smb://username@samba/shared/subdir", + "smbclient with non registered username authentication in public share", + "smb://username@samba/public/subdir", "file.txt", "", false, }, { - "smbclient without authentication", - "smb://samba/shared/subdir", + "smbclient without authentication in public share", + "smb://samba/public/subdir", "file.txt", "", false, }, { "local mounted smb shared file", - "smb://mnt/shared/file.txt", + "smb://mnt/public/file.txt", "file.txt", - "/mnt/shared", + "/mnt/public", true, }, { "local mounted smb shared directory", - "smb://mnt/shared/subdir", + "smb://mnt/public/subdir", "file.txt", - "/mnt/shared/subdir", + "/mnt/public/subdir", false, }, { - "non existent directory", - "smb://username:password@samba/shared/invalid", + "non existent directory in public share", + "smb://user:password@samba/public/invalid", "", "", true, @@ -176,36 +180,43 @@ func TestSmbGetter_GetFile(t *testing.T) { fail bool }{ { - "smbclient with authentication", - "smb://username:password@samba/shared/file.txt", + "smbclient with registered authentication in public share", + "smb://user:password@samba/public/file.txt", "file.txt", "", false, }, { - "smbclient with authentication and subdirectory", - "smb://username:password@samba/shared/subdir/file.txt", + "smbclient with registered authentication and subdirectory in public share", + "smb://user:password@samba/public/subdir/file.txt", "file.txt", "", false, }, { - "smbclient with only username authentication", - "smb://username@samba/shared/file.txt", + "smbclient with only registered username authentication in public share", + "smb://user@samba/public/file.txt", + "file.txt", + "", + true, + }, + { + "smbclient with non registered username authentication in public share", + "smb://username@samba/public/file.txt", "file.txt", "", false, }, { - "smbclient without authentication", - "smb://samba/shared/file.txt", + "smbclient without authentication in public share", + "smb://samba/public/file.txt", "file.txt", "", false, }, { - "smbclient get directory", - "smb://username:password@samba/shared/subdir", + "smbclient get directory in public share", + "smb://user:password@samba/public/subdir", "", "", true, @@ -225,8 +236,8 @@ func TestSmbGetter_GetFile(t *testing.T) { true, }, { - "non existent file", - "smb://username:password@samba/shared/invalidfile.txt", + "non existent file in public share", + "smb://user:password@samba/public/invalidfile.txt", "", "", true, @@ -334,32 +345,32 @@ func TestSmbGetter_Mode(t *testing.T) { fail bool }{ { - "smbclient modefile for existing file", - "smb://username:password@samba/shared/file.txt", + "smbclient modefile for existing file in authenticated public share", + "smb://user:password@samba/public/file.txt", ModeFile, "file.txt", "", false, }, { - "smbclient modedir for existing directory", - "smb://username:password@samba/shared/subdir", + "smbclient modedir for existing directory in authenticated public share", + "smb://user:password@samba/public/subdir", ModeDir, "", "", false, }, { - "mode fail for non existent directory", - "smb://username:password@samba/shared/invaliddir", + "mode fail for non existent directory in authenticated public share", + "smb://user:password@samba/public/invaliddir", 0, "", "", true, }, { - "mode fail for non existent file", - "smb://username:password@samba/shared/invalidfile.txt", + "mode fail for non existent file in authenticated public share", + "smb://user:password@samba/public/invalidfile.txt", 0, "", "", From acc21a104e627d8bba7954295f581db0e344bc3f Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 21 Apr 2020 16:30:14 +0200 Subject: [PATCH 054/109] test smb authentication in private shared folder --- Makefile | 1 + README.md | 4 +- docker-compose.yml | 2 +- get_smb_test.go | 113 ++++++++++++++++++++++++++++++++++++--------- 4 files changed, 94 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index a9b4b5759..9131824f2 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ smbtests-prepare: @docker cp ./ gogetter:/go-getter/ @docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient" @docker exec -it samba bash -c "echo 'Hello' > data/file.txt && mkdir -p data/subdir && echo 'Hello' > data/subdir/file.txt" + @docker exec -it samba bash -c "echo 'Hello' > mnt/file.txt && mkdir -p mnt/subdir && echo 'Hello' > mnt/subdir/file.txt" smbtests: diff --git a/README.md b/README.md index 341776629..62c95221d 100644 --- a/README.md +++ b/README.md @@ -376,8 +376,8 @@ Some examples for addressing the scheme: #### SMB Testing The test for `get_smb.go` requires a smb server running which can be started inside a docker container by -running `make start-smb`. Once the container is up the shared folder can be accessed via `smb:///shared/` -by another container or machine in the same network. +running `make start-smb`. Once the container is up the shared folder can be accessed via `smb:///public/` or +`smb://user:password@/private/` by another container or machine in the same network. To run the tests inside `get_smb_test.go`, prepare the environment with `make smbtests-prepare`. On prepare some mock files and directories will be added to the shared folder and a go-getter container will start together with the samba server. diff --git a/docker-compose.yml b/docker-compose.yml index 930d0c167..8c9d6445b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: ports: - "139:139/tcp" # default smb port - "445:445/tcp" # default smb port - command: '-u "user;password" -s "public;/data"' + command: '-s "public;/data" -s "private;/mnt;yes;no;no;user" -u "user;password" -p ' gogetter: image: golang:latest diff --git a/get_smb_test.go b/get_smb_test.go index 346f103b5..5ba2292d4 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -23,22 +23,22 @@ func TestSmbGetter_Get(t *testing.T) { fail bool }{ { - "smbclient with registered authentication in public share", - "smb://user:password@samba/public/subdir", + "smbclient with registered authentication in private share", + "smb://user:password@samba/private/subdir", "file.txt", "", false, }, { - "smbclient with registered authentication with file in public share", - "smb://user:password@samba/public/subdir/file.txt", + "smbclient with registered authentication with file in private share", + "smb://user:password@samba/private/subdir/file.txt", "file.txt", "", true, }, { - "smbclient with only registered username authentication in public share", - "smb://user@samba/public/subdir", + "smbclient with only registered username authentication in private share", + "smb://user@samba/private/subdir", "file.txt", "", true, @@ -50,6 +50,13 @@ func TestSmbGetter_Get(t *testing.T) { "", false, }, + { + "smbclient without authentication in private share", + "smb://samba/private/subdir", + "file.txt", + "", + true, + }, { "smbclient without authentication in public share", "smb://samba/public/subdir", @@ -71,9 +78,16 @@ func TestSmbGetter_Get(t *testing.T) { "/mnt/public/subdir", false, }, + { + "non existent directory in private share", + "smb://user:password@samba/private/invalid", + "", + "", + true, + }, { "non existent directory in public share", - "smb://user:password@samba/public/invalid", + "smb://samba/public/invalid", "", "", true, @@ -180,22 +194,22 @@ func TestSmbGetter_GetFile(t *testing.T) { fail bool }{ { - "smbclient with registered authentication in public share", - "smb://user:password@samba/public/file.txt", + "smbclient with registered authentication in private share", + "smb://user:password@samba/private/file.txt", "file.txt", "", false, }, { - "smbclient with registered authentication and subdirectory in public share", - "smb://user:password@samba/public/subdir/file.txt", + "smbclient with registered authentication and subdirectory in private share", + "smb://user:password@samba/private/subdir/file.txt", "file.txt", "", false, }, { - "smbclient with only registered username authentication in public share", - "smb://user@samba/public/file.txt", + "smbclient with only registered username authentication in private share", + "smb://user@samba/private/file.txt", "file.txt", "", true, @@ -214,9 +228,23 @@ func TestSmbGetter_GetFile(t *testing.T) { "", false, }, + { + "smbclient without authentication in private share", + "smb://samba/private/file.txt", + "file.txt", + "", + true, + }, + { + "smbclient get directory in private share", + "smb://user:password@samba/private/subdir", + "", + "", + true, + }, { "smbclient get directory in public share", - "smb://user:password@samba/public/subdir", + "smb://samba/public/subdir", "", "", true, @@ -235,9 +263,16 @@ func TestSmbGetter_GetFile(t *testing.T) { "/mnt/shared/subdir", true, }, + { + "non existent file in private share", + "smb://user:password@samba/private/invalidfile.txt", + "", + "", + true, + }, { "non existent file in public share", - "smb://user:password@samba/public/invalidfile.txt", + "smb://samba/public/invalidfile.txt", "", "", true, @@ -345,32 +380,64 @@ func TestSmbGetter_Mode(t *testing.T) { fail bool }{ { - "smbclient modefile for existing file in authenticated public share", - "smb://user:password@samba/public/file.txt", + "smbclient modefile for existing file in authenticated private share", + "smb://user:password@samba/private/file.txt", ModeFile, "file.txt", "", false, }, { - "smbclient modedir for existing directory in authenticated public share", - "smb://user:password@samba/public/subdir", + "smbclient modedir for existing directory in authenticated private share", + "smb://user:password@samba/private/subdir", + ModeDir, + "", + "", + false, + }, + { + "mode fail for non existent directory in authenticated private share", + "smb://user:password@samba/private/invaliddir", + 0, + "", + "", + true, + }, + { + "mode fail for non existent file in authenticated private share", + "smb://user:password@samba/private/invalidfile.txt", + 0, + "", + "", + true, + }, + { + "smbclient modefile for existing file in public share", + "smb://samba/public/file.txt", + ModeFile, + "file.txt", + "", + false, + }, + { + "smbclient modedir for existing directory in public share", + "smb://samba/public/subdir", ModeDir, "", "", false, }, { - "mode fail for non existent directory in authenticated public share", - "smb://user:password@samba/public/invaliddir", + "mode fail for non existent directory in public share", + "smb://samba/public/invaliddir", 0, "", "", true, }, { - "mode fail for non existent file in authenticated public share", - "smb://user:password@samba/public/invalidfile.txt", + "mode fail for non existent file in public share", + "smb://samba/public/invalidfile.txt", 0, "", "", From 5fa9370af4c2e8ed97a2955e762ab91ddbcb9c7a Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 21 Apr 2020 16:48:00 +0200 Subject: [PATCH 055/109] try to mount samba shared folder --- .circleci/config.yml | 5 +- docker-compose.yml | 4 + get_smb_test.go | 196 ++++++++++++++----------------------------- 3 files changed, 72 insertions(+), 133 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 80ac8cdde..51f50d926 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -83,7 +83,10 @@ jobs: command: | docker exec -it samba bash -c "echo 'Hello' > data/file.txt \ && mkdir -p data/subdir \ - && echo 'Hello' > data/subdir/file.txt" + && echo 'Hello' > data/subdir/file.txt \ + && echo 'Hello' > mnt/file.txt \ + && mkdir -p mnt/subdir \ + && echo 'Hello' > mnt/subdir/file.txt" docker cp ./ gogetter:/go-getter/ docker exec -it gogetter bash -c "go mod download \ && apt-get update \ diff --git a/docker-compose.yml b/docker-compose.yml index 8c9d6445b..a52d1c6ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,8 @@ services: ports: - "139:139/tcp" # default smb port - "445:445/tcp" # default smb port + volumes: + - /mnt:/mnt:z command: '-s "public;/data" -s "private;/mnt;yes;no;no;user" -u "user;password" -p ' gogetter: @@ -22,6 +24,8 @@ services: networks: - default working_dir: /go-getter + volumes: + - /mnt:/mnt command: tail -f /dev/null networks: diff --git a/get_smb_test.go b/get_smb_test.go index 5ba2292d4..7eac086a3 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -16,125 +16,100 @@ func TestSmbGetter_Get(t *testing.T) { smbTestsPreCheck(t) tests := []struct { - name string - rawURL string - file string - createDir string - fail bool + name string + rawURL string + file string + mounted bool + fail bool }{ { "smbclient with registered authentication in private share", "smb://user:password@samba/private/subdir", "file.txt", - "", + false, false, }, { "smbclient with registered authentication with file in private share", "smb://user:password@samba/private/subdir/file.txt", "file.txt", - "", + false, true, }, { "smbclient with only registered username authentication in private share", "smb://user@samba/private/subdir", "file.txt", - "", + false, true, }, { "smbclient with non registered username authentication in public share", "smb://username@samba/public/subdir", "file.txt", - "", + false, false, }, { "smbclient without authentication in private share", "smb://samba/private/subdir", "file.txt", - "", + false, true, }, { "smbclient without authentication in public share", "smb://samba/public/subdir", "file.txt", - "", false, - }, - { - "local mounted smb shared file", - "smb://mnt/public/file.txt", - "file.txt", - "/mnt/public", - true, - }, - { - "local mounted smb shared directory", - "smb://mnt/public/subdir", - "file.txt", - "/mnt/public/subdir", false, }, + //{ + // "local mounted smb shared file", + // "smb://mnt/file.txt", + // "file.txt", + // true, + // true, + //}, + //{ + // "local mounted smb shared directory", + // "smb://mnt/subdir", + // "file.txt", + // true, + // false, + //}, { "non existent directory in private share", "smb://user:password@samba/private/invalid", "", - "", + false, true, }, { "non existent directory in public share", "smb://samba/public/invalid", "", - "", + false, true, }, { "no hostname provided", "smb://", "", - "", + false, true, }, { "no filepath provided", "smb://samba", "", - "", + false, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.createDir != "" { - // mock mounted folder by creating one - err := os.MkdirAll(tt.createDir, 0755) - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - - if tt.file != "" { - f, err := os.Create(filepath.Join(tt.createDir, tt.file)) - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - defer f.Close() - - // Write content to assert later - _, err = f.WriteString("Hello\n") - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - f.Sync() - } - - defer os.RemoveAll(tt.createDir) - } - dst := tempDir(t) defer os.RemoveAll(dst) @@ -159,7 +134,7 @@ func TestSmbGetter_Get(t *testing.T) { } if !tt.fail { - if tt.createDir != "" { + if tt.mounted { // Verify the destination folder is a symlink to the mounted one fi, err := os.Lstat(dst) if err != nil { @@ -187,139 +162,114 @@ func TestSmbGetter_GetFile(t *testing.T) { smbTestsPreCheck(t) tests := []struct { - name string - rawURL string - file string - createDir string - fail bool + name string + rawURL string + file string + mounted bool + fail bool }{ { "smbclient with registered authentication in private share", "smb://user:password@samba/private/file.txt", "file.txt", - "", + false, false, }, { "smbclient with registered authentication and subdirectory in private share", "smb://user:password@samba/private/subdir/file.txt", "file.txt", - "", + false, false, }, { "smbclient with only registered username authentication in private share", "smb://user@samba/private/file.txt", "file.txt", - "", + false, true, }, { "smbclient with non registered username authentication in public share", "smb://username@samba/public/file.txt", "file.txt", - "", + false, false, }, { "smbclient without authentication in public share", "smb://samba/public/file.txt", "file.txt", - "", + false, false, }, { "smbclient without authentication in private share", "smb://samba/private/file.txt", "file.txt", - "", + false, true, }, { "smbclient get directory in private share", "smb://user:password@samba/private/subdir", "", - "", + false, true, }, { "smbclient get directory in public share", "smb://samba/public/subdir", "", - "", + false, true, }, { "local mounted smb shared file", - "smb://mnt/shared/file.txt", + "smb://mnt/file.txt", "file.txt", - "/mnt/shared", + true, false, }, { "local mounted smb shared directory", - "smb://mnt/shared/subdir", + "smb://mnt/subdir", "", - "/mnt/shared/subdir", + true, true, }, { "non existent file in private share", "smb://user:password@samba/private/invalidfile.txt", "", - "", + false, true, }, { "non existent file in public share", "smb://samba/public/invalidfile.txt", "", - "", + false, true, }, { "no hostname provided", "smb://", "", - "", + false, true, }, { "no filepath provided", "smb://samba", "", - "", + false, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.createDir != "" { - // mock mounted folder by creating one - err := os.MkdirAll(tt.createDir, 0755) - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - - if tt.file != "" { - f, err := os.Create(filepath.Join(tt.createDir, tt.file)) - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - defer f.Close() - - // Write content to assert later - _, err = f.WriteString("Hello\n") - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - f.Sync() - } - - defer os.RemoveAll(tt.createDir) - } - dst := tempDir(t) defer os.RemoveAll(dst) @@ -344,7 +294,7 @@ func TestSmbGetter_GetFile(t *testing.T) { } if !tt.fail { - if tt.createDir != "" { + if tt.mounted { // Verify the destination folder is a symlink to the mounted one fi, err := os.Lstat(dst) if err != nil { @@ -376,7 +326,7 @@ func TestSmbGetter_Mode(t *testing.T) { rawURL string expectedMode Mode file string - createDir string + mounted bool fail bool }{ { @@ -384,7 +334,7 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://user:password@samba/private/file.txt", ModeFile, "file.txt", - "", + false, false, }, { @@ -392,7 +342,7 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://user:password@samba/private/subdir", ModeDir, "", - "", + false, false, }, { @@ -400,7 +350,7 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://user:password@samba/private/invaliddir", 0, "", - "", + false, true, }, { @@ -408,7 +358,7 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://user:password@samba/private/invalidfile.txt", 0, "", - "", + false, true, }, { @@ -416,7 +366,7 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://samba/public/file.txt", ModeFile, "file.txt", - "", + false, false, }, { @@ -424,7 +374,7 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://samba/public/subdir", ModeDir, "", - "", + false, false, }, { @@ -432,7 +382,7 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://samba/public/invaliddir", 0, "", - "", + false, true, }, { @@ -440,47 +390,29 @@ func TestSmbGetter_Mode(t *testing.T) { "smb://samba/public/invalidfile.txt", 0, "", - "", + false, true, }, { "local mount modefile for existing file", - "smb://mnt/shared/file.txt", + "smb://mnt/file.txt", ModeFile, "file.txt", - "/mnt/shared", + true, false, }, { "local mount modedir for existing directory", - "smb://mnt/shared/subdir", + "smb://mnt/subdir", ModeDir, "", - "/mnt/shared/subdir", + true, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.createDir != "" { - // mock mounted folder by creating one - err := os.MkdirAll(tt.createDir, 0755) - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - - if tt.file != "" { - f, err := os.Create(filepath.Join(tt.createDir, tt.file)) - if err != nil { - t.Fatalf("err: %s", err.Error()) - } - defer f.Close() - } - - defer os.RemoveAll(tt.createDir) - } - url, err := urlhelper.Parse(tt.rawURL) if err != nil { t.Fatalf("err: %s", err.Error()) From 33158fed15dbe0468ee38c2d82b911b46a030516 Mon Sep 17 00:00:00 2001 From: Moss Date: Wed, 22 Apr 2020 10:33:41 +0200 Subject: [PATCH 056/109] use dockerfile instead --- .circleci/config.yml | 18 +++--------------- Dockerfile | 9 +++++++++ Dockerfile-smbserver | 9 +++++++++ Makefile | 8 +++----- docker-compose.yml | 10 ++++++---- get_smb_test.go | 28 ++++++++++++++-------------- 6 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 Dockerfile create mode 100644 Dockerfile-smbserver diff --git a/.circleci/config.yml b/.circleci/config.yml index 51f50d926..1aad0c99a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,31 +70,19 @@ jobs: docker-compose==1.12.0 - run: - name: start smb server and gogetter containers + name: build and start smb server and gogetter containers command: | + docker-compose build docker-compose up -d - run: name: wait for containers to start command: sleep 60 - - run: - name: prepare server and client to run tests - command: | - docker exec -it samba bash -c "echo 'Hello' > data/file.txt \ - && mkdir -p data/subdir \ - && echo 'Hello' > data/subdir/file.txt \ - && echo 'Hello' > mnt/file.txt \ - && mkdir -p mnt/subdir \ - && echo 'Hello' > mnt/subdir/file.txt" - docker cp ./ gogetter:/go-getter/ - docker exec -it gogetter bash -c "go mod download \ - && apt-get update \ - && apt-get -y install smbclient" - - run: name: run smb getter tests command: | + docker exec -it samba bash -c "echo 'Hello' > /mnt/file.txt && mkdir -p /mnt/subdir && echo 'Hello' > /mnt/subdir/file.txt" docker exec -it gogetter bash -c "env ACC_SMB_TEST=1 go test -v ./... -run=TestSmbGetter_" workflows: diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..80b4612ec --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +# Dockerfile to create a go-getter container with smbclient dependency that is used by the get_smb.go tests +FROM golang:latest + +COPY . /go-getter +WORKDIR /go-getter + +RUN go mod download +RUN apt-get update +RUN apt-get -y install smbclient \ No newline at end of file diff --git a/Dockerfile-smbserver b/Dockerfile-smbserver new file mode 100644 index 000000000..ff7229834 --- /dev/null +++ b/Dockerfile-smbserver @@ -0,0 +1,9 @@ +# Dockerfile to create a smb server that is used by the get_smb.go tests +FROM dperson/samba + +# Create shared folders +RUN mkdir -p /public && mkdir -p /private + +# Create shared files and directories under the shared folders (data and mnt) +RUN echo 'Hello' > /public/file.txt && mkdir -p /public/subdir && echo 'Hello' > /public/subdir/file.txt +RUN echo 'Hello' > /private/file.txt && mkdir -p /private/subdir && echo 'Hello' > /private/subdir/file.txt \ No newline at end of file diff --git a/Makefile b/Makefile index 9131824f2..81c6cd27d 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,12 @@ start-smb: + @docker-compose build docker-compose up -d samba smbtests-prepare: + @docker-compose build @docker-compose up -d @sleep 60 - @docker cp ./ gogetter:/go-getter/ - @docker exec -it gogetter bash -c "go mod download && apt-get update && apt-get -y install smbclient" - @docker exec -it samba bash -c "echo 'Hello' > data/file.txt && mkdir -p data/subdir && echo 'Hello' > data/subdir/file.txt" - @docker exec -it samba bash -c "echo 'Hello' > mnt/file.txt && mkdir -p mnt/subdir && echo 'Hello' > mnt/subdir/file.txt" - + @docker exec -it samba bash -c "echo 'Hello' > /mnt/file.txt && mkdir -p /mnt/subdir && echo 'Hello' > /mnt/subdir/file.txt" smbtests: @docker cp ./ gogetter:/go-getter/ diff --git a/docker-compose.yml b/docker-compose.yml index a52d1c6ef..75799bc54 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,9 @@ version: '3.2' services: samba: - image: dperson/samba + build: + dockerfile: Dockerfile-smbserver + context: . container_name: samba environment: USERID: "0" @@ -14,16 +16,16 @@ services: - "445:445/tcp" # default smb port volumes: - /mnt:/mnt:z - command: '-s "public;/data" -s "private;/mnt;yes;no;no;user" -u "user;password" -p ' + command: '-s "public;/public" -s "private;/private;yes;no;no;user" -u "user;password" -p' gogetter: - image: golang:latest + build: + context: . container_name: gogetter depends_on: - samba networks: - default - working_dir: /go-getter volumes: - /mnt:/mnt command: tail -f /dev/null diff --git a/get_smb_test.go b/get_smb_test.go index 7eac086a3..5e9f19b33 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -64,20 +64,20 @@ func TestSmbGetter_Get(t *testing.T) { false, false, }, - //{ - // "local mounted smb shared file", - // "smb://mnt/file.txt", - // "file.txt", - // true, - // true, - //}, - //{ - // "local mounted smb shared directory", - // "smb://mnt/subdir", - // "file.txt", - // true, - // false, - //}, + { + "local mounted smb shared file", + "smb://mnt/file.txt", + "file.txt", + true, + true, + }, + { + "local mounted smb shared directory", + "smb://mnt/subdir", + "file.txt", + true, + false, + }, { "non existent directory in private share", "smb://user:password@samba/private/invalid", From 62a9f0a23e9f9703d3c798acb36142c43f8ce7f6 Mon Sep 17 00:00:00 2001 From: Moss Date: Wed, 22 Apr 2020 11:31:34 +0200 Subject: [PATCH 057/109] remove unecessary code from tests --- get_smb_test.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/get_smb_test.go b/get_smb_test.go index 5e9f19b33..44030d1a8 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -325,88 +325,66 @@ func TestSmbGetter_Mode(t *testing.T) { name string rawURL string expectedMode Mode - file string - mounted bool fail bool }{ { "smbclient modefile for existing file in authenticated private share", "smb://user:password@samba/private/file.txt", ModeFile, - "file.txt", - false, false, }, { "smbclient modedir for existing directory in authenticated private share", "smb://user:password@samba/private/subdir", ModeDir, - "", - false, false, }, { "mode fail for non existent directory in authenticated private share", "smb://user:password@samba/private/invaliddir", 0, - "", - false, true, }, { "mode fail for non existent file in authenticated private share", "smb://user:password@samba/private/invalidfile.txt", 0, - "", - false, true, }, { "smbclient modefile for existing file in public share", "smb://samba/public/file.txt", ModeFile, - "file.txt", - false, false, }, { "smbclient modedir for existing directory in public share", "smb://samba/public/subdir", ModeDir, - "", - false, false, }, { "mode fail for non existent directory in public share", "smb://samba/public/invaliddir", 0, - "", - false, true, }, { "mode fail for non existent file in public share", "smb://samba/public/invalidfile.txt", 0, - "", - false, true, }, { "local mount modefile for existing file", "smb://mnt/file.txt", ModeFile, - "file.txt", - true, false, }, { "local mount modedir for existing directory", "smb://mnt/subdir", ModeDir, - "", - true, false, }, } From 075c9733a67dbb9475ab5a5da1c49d85bb12ded3 Mon Sep 17 00:00:00 2001 From: Moss Date: Wed, 22 Apr 2020 14:55:40 +0200 Subject: [PATCH 058/109] fix destination path --- get_smb.go | 43 +++++++++++++++++++++++++++++++------------ get_smb_test.go | 26 +++++++------------------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/get_smb.go b/get_smb.go index 64d4bb626..3728cb9fe 100644 --- a/get_smb.go +++ b/get_smb.go @@ -7,6 +7,7 @@ import ( "net/url" "os" "os/exec" + "path/filepath" "regexp" "runtime" "strings" @@ -135,6 +136,21 @@ func (g *SmbGetter) smbclientGet(req *Request) error { // download everything that's inside the directory (files and subdirectories) smbclientCmd := baseCmd + " --command 'prompt OFF;recurse ON; mget *'" + + if req.Dst != "" { + _, err := os.Lstat(req.Dst) + if err != nil { + if os.IsNotExist(err) { + // Create destination folder if it doesn't exists + if err := os.MkdirAll(req.Dst, 0755); err != nil { + return fmt.Errorf("failed to creat destination path: %s", err.Error()) + } + } else { + return err + } + } + } + _, err = runSmbClientCommand(smbclientCmd, req.Dst) return err } @@ -208,7 +224,21 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { // download file smbclientCmd := baseCmd + " --command " + fmt.Sprintf("'get %s'", file) - _, err = runSmbClientCommand(smbclientCmd, req.Dst) + if req.Dst != "" { + _, err := os.Lstat(req.Dst) + if err != nil { + if os.IsNotExist(err) { + // Create destination folder if it doesn't exists + if err := os.MkdirAll(filepath.Dir(req.Dst), 0755); err != nil { + return fmt.Errorf("failed to creat destination path: %s", err.Error()) + } + } else { + return err + } + } + smbclientCmd = baseCmd + " --command " + fmt.Sprintf("'get %s %s'", file, req.Dst) + } + _, err = runSmbClientCommand(smbclientCmd, "") return err } @@ -265,17 +295,6 @@ func runSmbClientCommand(smbclientCmd string, dst string) (string, error) { cmd := exec.Command("bash", "-c", smbclientCmd) if dst != "" { - _, err := os.Lstat(dst) - if err != nil { - if os.IsNotExist(err) { - // Create destination folder if it doesn't exists - if err := os.MkdirAll(dst, os.ModePerm); err != nil { - return "", fmt.Errorf("failed to creat destination path: %s", err.Error()) - } - } else { - return "", err - } - } cmd.Dir = dst } diff --git a/get_smb_test.go b/get_smb_test.go index 44030d1a8..75f8b851d 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -278,7 +278,7 @@ func TestSmbGetter_GetFile(t *testing.T) { t.Fatalf("err: %s", err.Error()) } req := &Request{ - Dst: dst, + Dst: filepath.Join(dst, tt.file), u: url, } @@ -294,25 +294,13 @@ func TestSmbGetter_GetFile(t *testing.T) { } if !tt.fail { - if tt.mounted { - // Verify the destination folder is a symlink to the mounted one - fi, err := os.Lstat(dst) - if err != nil { - t.Fatalf("err: %s", err) - } - if fi.Mode()&os.ModeSymlink == 0 { - t.Fatal("destination is not a symlink") - } - // Verify the file exists - assertContents(t, dst, "Hello\n") - } else { - // Verify if the file was successfully downloaded - // and exists at the destination folder - mainPath := filepath.Join(dst, tt.file) - if _, err := os.Stat(mainPath); err != nil { - t.Fatalf("err: %s", err) - } + // Verify if the file was successfully downloaded + // and exists at the destination folder + mainPath := filepath.Join(dst, tt.file) + if _, err := os.Stat(mainPath); err != nil { + t.Fatalf("err: %s", err) } + } }) } From 02a18570b99ee7afbad2f4dc94582ec0a95a94c0 Mon Sep 17 00:00:00 2001 From: Moss Date: Wed, 22 Apr 2020 15:16:55 +0200 Subject: [PATCH 059/109] improve error msg --- get_smb.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/get_smb.go b/get_smb.go index 3728cb9fe..dc04ade23 100644 --- a/get_smb.go +++ b/get_smb.go @@ -47,7 +47,7 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { } result = multierror.Append(result, err) - return 0, fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", result.Error()) + return 0, fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary). \n err: %s", result.Error()) } func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { @@ -115,7 +115,7 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { os.Remove(req.Dst) } - return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", result.Error()) + return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary). \n err: %s", result.Error()) } func (g *SmbGetter) smbclientGet(req *Request) error { @@ -192,7 +192,7 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { os.Remove(req.Dst) } - return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed. \n err: %s", result.Error()) + return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary). \n err: %s", result.Error()) } func (g *SmbGetter) smbclientGetFile(req *Request) error { From d670e78fe37e420cd76aa0070b8fa01bee2221c1 Mon Sep 17 00:00:00 2001 From: Sylvia Moss Date: Thu, 23 Apr 2020 14:26:15 +0200 Subject: [PATCH 060/109] Fix comment typo Co-Authored-By: Megan Marsh --- get_smb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get_smb.go b/get_smb.go index dc04ade23..0029329a9 100644 --- a/get_smb.go +++ b/get_smb.go @@ -141,7 +141,7 @@ func (g *SmbGetter) smbclientGet(req *Request) error { _, err := os.Lstat(req.Dst) if err != nil { if os.IsNotExist(err) { - // Create destination folder if it doesn't exists + // Create destination folder if it doesn't exist if err := os.MkdirAll(req.Dst, 0755); err != nil { return fmt.Errorf("failed to creat destination path: %s", err.Error()) } From 7f77866b95e2fec457c5913a13eee12e15f7905b Mon Sep 17 00:00:00 2001 From: Sylvia Moss Date: Thu, 23 Apr 2020 14:26:41 +0200 Subject: [PATCH 061/109] Fix error msg typo Co-Authored-By: Megan Marsh --- get_smb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get_smb.go b/get_smb.go index 0029329a9..68be0fbca 100644 --- a/get_smb.go +++ b/get_smb.go @@ -143,7 +143,7 @@ func (g *SmbGetter) smbclientGet(req *Request) error { if os.IsNotExist(err) { // Create destination folder if it doesn't exist if err := os.MkdirAll(req.Dst, 0755); err != nil { - return fmt.Errorf("failed to creat destination path: %s", err.Error()) + return fmt.Errorf("failed to create destination path: %s", err.Error()) } } else { return err From de18f12cbef6fe4bc6ce3c6a6ab2e54222e4463e Mon Sep 17 00:00:00 2001 From: Sylvia Moss Date: Thu, 23 Apr 2020 14:27:07 +0200 Subject: [PATCH 062/109] Fix comment typo Co-Authored-By: Megan Marsh --- get_smb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get_smb.go b/get_smb.go index 68be0fbca..21bd9fc8a 100644 --- a/get_smb.go +++ b/get_smb.go @@ -160,7 +160,7 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { return fmt.Errorf(basePathError) } - // If dst folder doesn't exists, we need to remove the created on later in case of failures + // If dst folder doesn't exist, we need to remove the created one later in case of failures dstExisted := false if req.Dst != "" { if _, err := os.Lstat(req.Dst); err == nil { From 39c210b7837406ef4913e81a99ec69835fc11047 Mon Sep 17 00:00:00 2001 From: Sylvia Moss Date: Thu, 23 Apr 2020 14:27:29 +0200 Subject: [PATCH 063/109] Fix comment typo Co-Authored-By: Megan Marsh --- get_smb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get_smb.go b/get_smb.go index 21bd9fc8a..d79dd922f 100644 --- a/get_smb.go +++ b/get_smb.go @@ -228,7 +228,7 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { _, err := os.Lstat(req.Dst) if err != nil { if os.IsNotExist(err) { - // Create destination folder if it doesn't exists + // Create destination folder if it doesn't exist if err := os.MkdirAll(filepath.Dir(req.Dst), 0755); err != nil { return fmt.Errorf("failed to creat destination path: %s", err.Error()) } From 097b7773d72ce51c8b9fade815392f0ad82723f5 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 23 Apr 2020 16:19:12 +0200 Subject: [PATCH 064/109] improve error messaging --- get_smb.go | 69 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/get_smb.go b/get_smb.go index dc04ade23..2976044ab 100644 --- a/get_smb.go +++ b/get_smb.go @@ -22,11 +22,9 @@ type SmbGetter struct { getter } -const basePathError = "samba path should contain valid host, filepath, and authentication if necessary (smb://:@/)" - func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.Host == "" || u.Path == "" { - return 0, fmt.Errorf(basePathError) + return 0, new(smbPathError) } // Look in a possible local mount of shared folder @@ -47,11 +45,11 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { } result = multierror.Append(result, err) - return 0, fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary). \n err: %s", result.Error()) + return 0, &smbGeneralError{result} } func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { - hostPath, filePath, err := findHostAndFilePath(u) + hostPath, filePath, err := g.findHostAndFilePath(u) if err != nil { return 0, err } @@ -66,9 +64,9 @@ func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { filePath = "." } - baseCmd := smbclientBaseCmd(u.User, hostPath, filePath) + baseCmd := g.smbclientBaseCmd(u.User, hostPath, filePath) // check if file exists in the smb shared folder and check the mode - isDir, err := isDirectory(baseCmd, file) + isDir, err := g.isDirectory(baseCmd, file) if err != nil { return 0, err } @@ -80,7 +78,7 @@ func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { func (g *SmbGetter) Get(ctx context.Context, req *Request) error { if req.u.Host == "" || req.u.Path == "" { - return fmt.Errorf(basePathError) + return new(smbPathError) } // If dst folder doesn't exists, we need to remove the created on later in case of failures @@ -115,18 +113,18 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { os.Remove(req.Dst) } - return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary). \n err: %s", result.Error()) + return &smbGeneralError{result} } func (g *SmbGetter) smbclientGet(req *Request) error { - hostPath, directory, err := findHostAndFilePath(req.u) + hostPath, directory, err := g.findHostAndFilePath(req.u) if err != nil { return err } - baseCmd := smbclientBaseCmd(req.u.User, hostPath, ".") + baseCmd := g.smbclientBaseCmd(req.u.User, hostPath, ".") // check directory exists in the smb shared folder and is a directory - isDir, err := isDirectory(baseCmd, directory) + isDir, err := g.isDirectory(baseCmd, directory) if err != nil { return err } @@ -151,13 +149,13 @@ func (g *SmbGetter) smbclientGet(req *Request) error { } } - _, err = runSmbClientCommand(smbclientCmd, req.Dst) + _, err = g.runSmbClientCommand(smbclientCmd, req.Dst) return err } func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { if req.u.Host == "" || req.u.Path == "" { - return fmt.Errorf(basePathError) + return new(smbPathError) } // If dst folder doesn't exists, we need to remove the created on later in case of failures @@ -192,11 +190,11 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { os.Remove(req.Dst) } - return fmt.Errorf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary). \n err: %s", result.Error()) + return &smbGeneralError{result} } func (g *SmbGetter) smbclientGetFile(req *Request) error { - hostPath, filePath, err := findHostAndFilePath(req.u) + hostPath, filePath, err := g.findHostAndFilePath(req.u) if err != nil { return err } @@ -212,9 +210,9 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { filePath = "." } - baseCmd := smbclientBaseCmd(req.u.User, hostPath, filePath) + baseCmd := g.smbclientBaseCmd(req.u.User, hostPath, filePath) // check file exists in the smb shared folder and is not a directory - isDir, err := isDirectory(baseCmd, file) + isDir, err := g.isDirectory(baseCmd, file) if err != nil { return err } @@ -238,11 +236,11 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { } smbclientCmd = baseCmd + " --command " + fmt.Sprintf("'get %s %s'", file, req.Dst) } - _, err = runSmbClientCommand(smbclientCmd, "") + _, err = g.runSmbClientCommand(smbclientCmd, "") return err } -func smbclientBaseCmd(used *url.Userinfo, hostPath string, fileDir string) string { +func(g *SmbGetter) smbclientBaseCmd(used *url.Userinfo, hostPath string, fileDir string) string { baseCmd := "smbclient -N" // Append auth user and password to baseCmd @@ -258,7 +256,7 @@ func smbclientBaseCmd(used *url.Userinfo, hostPath string, fileDir string) strin return baseCmd } -func findHostAndFilePath(u *url.URL) (string, string, error) { +func (g *SmbGetter) findHostAndFilePath(u *url.URL) (string, string, error) { // Host path hostPath := "//" + u.Host @@ -279,9 +277,9 @@ func findHostAndFilePath(u *url.URL) (string, string, error) { return hostPath, directories[1], nil } -func isDirectory(baseCmd string, object string) (bool, error) { +func (g *SmbGetter) isDirectory(baseCmd string, object string) (bool, error) { objectInfoCmd := baseCmd + " --command " + fmt.Sprintf("'allinfo %s'", object) - output, err := runSmbClientCommand(objectInfoCmd, "") + output, err := g.runSmbClientCommand(objectInfoCmd, "") if err != nil { return false, err } @@ -291,7 +289,7 @@ func isDirectory(baseCmd string, object string) (bool, error) { return strings.Contains(output, "attributes: D"), nil } -func runSmbClientCommand(smbclientCmd string, dst string) (string, error) { +func (g *SmbGetter) runSmbClientCommand(smbclientCmd string, dst string) (string, error) { cmd := exec.Command("bash", "-c", smbclientCmd) if dst != "" { @@ -318,3 +316,26 @@ func runSmbClientCommand(smbclientCmd string, dst string) (string, error) { } return buf.String(), fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) } + +type smbPathError struct { + Path string +} + +func (e *smbPathError) Error() string { + if e.Path == "" { + return "samba path should contain valid host, filepath, and authentication if necessary (smb://:@/)" + } + return fmt.Sprintf("samba path should contain valid host, filepath, and authentication if necessary (%s)", e.Path) +} + +type smbGeneralError struct { + err error +} + +func (e *smbGeneralError) Error() string { + if e != nil { + return fmt.Sprintf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary). \n err: %s", e.err.Error()) + } + return "one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary)." +} + From af77482e6d44146bc5ed8743ef9e76aaacb4319c Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 23 Apr 2020 16:23:30 +0200 Subject: [PATCH 065/109] update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 62c95221d..9bf9bf1b2 100644 --- a/README.md +++ b/README.md @@ -364,7 +364,8 @@ The tests for `get_gcs.go` require you to have GCP credentials set in your envir ### SMB (smb) -To access samba shared folder it's necessary to have a [smbclient](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) installed or a local mount of the shared folder. +To access samba shared folder, for Unix user it's necessary to have the [smbclient](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) installed or a local mount of the shared folder. +For windows users, only the access permission is necessary. Some examples for addressing the scheme: - smb://username:password@host/shared/dir (downloads directory content) From b350351074f3420333ff9be685548f57dbebf734 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 23 Apr 2020 16:26:28 +0200 Subject: [PATCH 066/109] fix format --- get_smb.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/get_smb.go b/get_smb.go index 1e784771c..5d2f708af 100644 --- a/get_smb.go +++ b/get_smb.go @@ -240,7 +240,7 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { return err } -func(g *SmbGetter) smbclientBaseCmd(used *url.Userinfo, hostPath string, fileDir string) string { +func (g *SmbGetter) smbclientBaseCmd(used *url.Userinfo, hostPath string, fileDir string) string { baseCmd := "smbclient -N" // Append auth user and password to baseCmd @@ -338,4 +338,3 @@ func (e *smbGeneralError) Error() string { } return "one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary)." } - From 447c3f6dff3802093cf752d4d3f825575482c150 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 24 Apr 2020 12:11:47 +0200 Subject: [PATCH 067/109] Add smbdetector and client tests --- .circleci/config.yml | 2 +- Makefile | 2 +- README.md | 7 ++- client_test.go | 117 +++++++++++++++++++++++++++++++++++++++++++ detect.go | 1 + detect_file.go | 6 +++ detect_smb.go | 21 ++++++++ get_smb_test.go | 26 +++------- source.go | 4 ++ 9 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 client_test.go create mode 100644 detect_smb.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 1aad0c99a..935bf75d4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -83,7 +83,7 @@ jobs: name: run smb getter tests command: | docker exec -it samba bash -c "echo 'Hello' > /mnt/file.txt && mkdir -p /mnt/subdir && echo 'Hello' > /mnt/subdir/file.txt" - docker exec -it gogetter bash -c "env ACC_SMB_TEST=1 go test -v ./... -run=TestSmbGetter_" + docker exec -it gogetter bash -c "env ACC_SMB_TEST=1 go test -v ./... -run=TestSmb_" workflows: version: 2 diff --git a/Makefile b/Makefile index 81c6cd27d..69392cdf3 100644 --- a/Makefile +++ b/Makefile @@ -10,4 +10,4 @@ smbtests-prepare: smbtests: @docker cp ./ gogetter:/go-getter/ - @docker exec -it gogetter bash -c "env ACC_SMB_TEST=1 go test -v ./... -run=TestSmbGetter_" \ No newline at end of file + @docker exec -it gogetter bash -c "env ACC_SMB_TEST=1 go test -v ./... -run=TestSmb_" \ No newline at end of file diff --git a/README.md b/README.md index 9bf9bf1b2..2dfab9c50 100644 --- a/README.md +++ b/README.md @@ -374,12 +374,17 @@ Some examples for addressing the scheme: - smb://username:password@host/shared/dir/file (downloads file) - smb://username@host/shared/dir/file - smb://host/shared/dir/file +- //host/shared/dir (downloads directory content) +- //host/shared/dir/file (downloads file) + +With and without the smb:// scheme, go-getter will first look for a local mount with file path as `/host/shared/dir` (Unix) or `//host/shared/dir` (Windows), and if the local mount is not available +it will then try to download the file/directory via `smbclient` command. #### SMB Testing The test for `get_smb.go` requires a smb server running which can be started inside a docker container by running `make start-smb`. Once the container is up the shared folder can be accessed via `smb:///public/` or `smb://user:password@/private/` by another container or machine in the same network. -To run the tests inside `get_smb_test.go`, prepare the environment with `make smbtests-prepare`. On prepare some +To run the tests inside `get_smb_test.go` and `client_test.go`, prepare the environment with `make smbtests-prepare`. On prepare some mock files and directories will be added to the shared folder and a go-getter container will start together with the samba server. Once the environment for testing is prepared, run `make smbtests` to run the tests. \ No newline at end of file diff --git a/client_test.go b/client_test.go new file mode 100644 index 000000000..1af8dfd9e --- /dev/null +++ b/client_test.go @@ -0,0 +1,117 @@ +package getter + +import ( + "context" + "log" + "os" + "path/filepath" + "testing" +) + +func TestSmb_ClientGet(t *testing.T) { + smbTestsPreCheck(t) + + tests := []struct { + name string + rawURL string + mode Mode + file string + mounted bool + fail bool + }{ + { + "smb scheme subdir with registered authentication in private share", + "smb://user:password@samba/private/subdir", + ModeDir, + "file.txt", + false, + false, + }, + { + "smb scheme file with registered authentication with file in private share", + "smb://user:password@samba/private/subdir/file.txt", + ModeFile, + "file.txt", + false, + false, + }, + { + "smb scheme file without authentication in public share", + "smb://samba/public/subdir/file.txt", + ModeFile, + "file.txt", + false, + false, + }, + { + "// prefix path local mounted smb shared file", + "//mnt/file.txt", + ModeFile, + "file.txt", + true, + false, + }, + { + "// prefix path local mounted smb shared directory", + "//mnt/subdir", + ModeDir, + "file.txt", + true, + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dst := tempDir(t) + defer os.RemoveAll(dst) + + if tt.mode == ModeFile { + dst = filepath.Join(dst, tt.file) + } + + log.Printf("MOSS dst %s", dst) + + req := &Request{ + Dst: dst, + Src: tt.rawURL, + Mode: tt.mode, + } + + result, err := DefaultClient.Get(context.Background(), req) + + fail := err != nil + if tt.fail != fail { + if fail { + t.Fatalf("err: unexpected error %s", err.Error()) + } + t.Fatalf("err: expecting to fail but it did not") + } + + if !tt.fail { + if result == nil { + t.Fatalf("err: get result should not be nil") + } + if result.Dst != dst { + t.Fatalf("err: expected destination: %s \n actual destination: %s", dst, result.Dst) + } + if tt.mounted && tt.mode == ModeDir { + // Verify the destination folder is a symlink to the mounted one + fi, err := os.Lstat(dst) + if err != nil { + t.Fatalf("err: %s", err) + } + if fi.Mode()&os.ModeSymlink == 0 { + t.Fatal("destination is not a symlink") + } + } + if tt.mode == ModeDir { + dst = filepath.Join(dst, tt.file) + } + // Verify if the file was successfully downloaded + // and exists at the destination folder + assertContents(t, dst, "Hello\n") + } + }) + } +} diff --git a/detect.go b/detect.go index 5bb750c9f..c82385e5e 100644 --- a/detect.go +++ b/detect.go @@ -28,6 +28,7 @@ func init() { new(S3Detector), new(GCSDetector), new(FileDetector), + new(SmbDetector), } } diff --git a/detect_file.go b/detect_file.go index 4ef41ea73..862c9f2c6 100644 --- a/detect_file.go +++ b/detect_file.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "runtime" + "strings" ) // FileDetector implements Detector to detect file paths. @@ -49,6 +50,11 @@ func (d *FileDetector) Detect(src, pwd string) (string, bool, error) { src = filepath.Join(pwd, src) } + if strings.HasPrefix(src, "//") { + // This is a valid smb path and will be also checked as local file by the SmbGetter + return src, false, nil + } + return fmtFileURL(src), true, nil } diff --git a/detect_smb.go b/detect_smb.go new file mode 100644 index 000000000..f27757cf7 --- /dev/null +++ b/detect_smb.go @@ -0,0 +1,21 @@ +package getter + +import ( + "fmt" + "strings" +) + +// SmbDetector implements Detector to detect smb paths with //. +type SmbDetector struct{} + +func (d *SmbDetector) Detect(src, pwd string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + if strings.HasPrefix(src, "//") { + // This is a valid smb path and will be also checked as local file by the SmbGetter + return fmt.Sprintf("smb:%s", src), true, nil + } + return "", false, nil +} diff --git a/get_smb_test.go b/get_smb_test.go index 75f8b851d..fd59bf56b 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -8,11 +8,11 @@ import ( "testing" ) -func TestSmbGetter_impl(t *testing.T) { +func TestSmb_GetterImpl(t *testing.T) { var _ Getter = new(SmbGetter) } -func TestSmbGetter_Get(t *testing.T) { +func TestSmb_GetterGet(t *testing.T) { smbTestsPreCheck(t) tests := []struct { @@ -143,22 +143,16 @@ func TestSmbGetter_Get(t *testing.T) { if fi.Mode()&os.ModeSymlink == 0 { t.Fatal("destination is not a symlink") } - // Verify the file exists - assertContents(t, filepath.Join(dst, tt.file), "Hello\n") - } else { - // Verify if the file was successfully downloaded - // and exists at the destination folder - mainPath := filepath.Join(dst, tt.file) - if _, err := os.Stat(mainPath); err != nil { - t.Fatalf("err: %s", err) - } } + // Verify if the file was successfully downloaded + // and exists at the destination folder + assertContents(t, filepath.Join(dst, tt.file), "Hello\n") } }) } } -func TestSmbGetter_GetFile(t *testing.T) { +func TestSmb_GetterGetFile(t *testing.T) { smbTestsPreCheck(t) tests := []struct { @@ -296,17 +290,13 @@ func TestSmbGetter_GetFile(t *testing.T) { if !tt.fail { // Verify if the file was successfully downloaded // and exists at the destination folder - mainPath := filepath.Join(dst, tt.file) - if _, err := os.Stat(mainPath); err != nil { - t.Fatalf("err: %s", err) - } - + assertContents(t, filepath.Join(dst, tt.file), "Hello\n") } }) } } -func TestSmbGetter_Mode(t *testing.T) { +func TestSmb_GetterMode(t *testing.T) { smbTestsPreCheck(t) tests := []struct { diff --git a/source.go b/source.go index dab6d400c..74e60dd37 100644 --- a/source.go +++ b/source.go @@ -15,6 +15,10 @@ import ( // proto://dom.com/path//path2?q=p => proto://dom.com/path?q=p, "path2" // func SourceDirSubdir(src string) (string, string) { + if strings.HasPrefix(src, "//") { + // This is valid for smb path + return src, "" + } // URL might contains another url in query parameters stop := len(src) From e520fc4911a24866d02b2a23c53ac7c94fb3ae78 Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 24 Apr 2020 15:18:30 +0200 Subject: [PATCH 068/109] Update readme --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2dfab9c50..982554756 100644 --- a/README.md +++ b/README.md @@ -364,8 +364,12 @@ The tests for `get_gcs.go` require you to have GCP credentials set in your envir ### SMB (smb) -To access samba shared folder, for Unix user it's necessary to have the [smbclient](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) installed or a local mount of the shared folder. -For windows users, only the access permission is necessary. +The SMB getter will try to get files from samba when a url is prefixed with `smb://`, for example `smb://foo/bar/dir`. + +The go-getter will try for a local mount of the path such as `/foo/bar/dir` (Unix) or `//foo/bar/dir` (Windows), +and will fallback to using the [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) command to download the file/dir. + +⚠️ On Unix when the smb share is not mounted, [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) needs to be installed for this to work. Some examples for addressing the scheme: - smb://username:password@host/shared/dir (downloads directory content) @@ -376,10 +380,8 @@ Some examples for addressing the scheme: - smb://host/shared/dir/file - //host/shared/dir (downloads directory content) - //host/shared/dir/file (downloads file) - -With and without the smb:// scheme, go-getter will first look for a local mount with file path as `/host/shared/dir` (Unix) or `//host/shared/dir` (Windows), and if the local mount is not available -it will then try to download the file/directory via `smbclient` command. - + + #### SMB Testing The test for `get_smb.go` requires a smb server running which can be started inside a docker container by running `make start-smb`. Once the container is up the shared folder can be accessed via `smb:///public/` or From aaafb5844c0e143c779be2f1b75b18c0435ee27c Mon Sep 17 00:00:00 2001 From: Moss Date: Fri, 24 Apr 2020 15:31:16 +0200 Subject: [PATCH 069/109] remove testing log --- client_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/client_test.go b/client_test.go index 1af8dfd9e..8afdd6824 100644 --- a/client_test.go +++ b/client_test.go @@ -2,7 +2,6 @@ package getter import ( "context" - "log" "os" "path/filepath" "testing" @@ -70,8 +69,6 @@ func TestSmb_ClientGet(t *testing.T) { dst = filepath.Join(dst, tt.file) } - log.Printf("MOSS dst %s", dst) - req := &Request{ Dst: dst, Src: tt.rawURL, From c326e24e18abb05e6ccf54d49c29dae7110f8a9b Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 27 Apr 2020 11:32:34 +0200 Subject: [PATCH 070/109] user smbclient command directly --- get_smb.go | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/get_smb.go b/get_smb.go index 5d2f708af..6eaa99ebc 100644 --- a/get_smb.go +++ b/get_smb.go @@ -64,9 +64,9 @@ func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { filePath = "." } - baseCmd := g.smbclientBaseCmd(u.User, hostPath, filePath) + cmdArgs := g.smbclientCmdArgs(u.User, hostPath, filePath) // check if file exists in the smb shared folder and check the mode - isDir, err := g.isDirectory(baseCmd, file) + isDir, err := g.isDirectory(cmdArgs, file) if err != nil { return 0, err } @@ -122,9 +122,9 @@ func (g *SmbGetter) smbclientGet(req *Request) error { return err } - baseCmd := g.smbclientBaseCmd(req.u.User, hostPath, ".") + cmdArgs := g.smbclientCmdArgs(req.u.User, hostPath, ".") // check directory exists in the smb shared folder and is a directory - isDir, err := g.isDirectory(baseCmd, directory) + isDir, err := g.isDirectory(cmdArgs, directory) if err != nil { return err } @@ -133,7 +133,8 @@ func (g *SmbGetter) smbclientGet(req *Request) error { } // download everything that's inside the directory (files and subdirectories) - smbclientCmd := baseCmd + " --command 'prompt OFF;recurse ON; mget *'" + cmdArgs = append(cmdArgs, "-c") + cmdArgs = append(cmdArgs, "prompt OFF;recurse ON; mget *") if req.Dst != "" { _, err := os.Lstat(req.Dst) @@ -149,7 +150,7 @@ func (g *SmbGetter) smbclientGet(req *Request) error { } } - _, err = g.runSmbClientCommand(smbclientCmd, req.Dst) + _, err = g.runSmbClientCommand(req.Dst, cmdArgs) return err } @@ -210,9 +211,9 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { filePath = "." } - baseCmd := g.smbclientBaseCmd(req.u.User, hostPath, filePath) + cmdArgs := g.smbclientCmdArgs(req.u.User, hostPath, filePath) // check file exists in the smb shared folder and is not a directory - isDir, err := g.isDirectory(baseCmd, file) + isDir, err := g.isDirectory(cmdArgs, file) if err != nil { return err } @@ -221,7 +222,7 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { } // download file - smbclientCmd := baseCmd + " --command " + fmt.Sprintf("'get %s'", file) + cmdArgs = append(cmdArgs, "-c") if req.Dst != "" { _, err := os.Lstat(req.Dst) if err != nil { @@ -234,14 +235,17 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { return err } } - smbclientCmd = baseCmd + " --command " + fmt.Sprintf("'get %s %s'", file, req.Dst) + cmdArgs = append(cmdArgs, fmt.Sprintf("get %s %s", file, req.Dst)) + } else { + cmdArgs = append(cmdArgs, fmt.Sprintf("get %s", file)) } - _, err = g.runSmbClientCommand(smbclientCmd, "") + + _, err = g.runSmbClientCommand("", cmdArgs) return err } -func (g *SmbGetter) smbclientBaseCmd(used *url.Userinfo, hostPath string, fileDir string) string { - baseCmd := "smbclient -N" +func (g *SmbGetter) smbclientCmdArgs(used *url.Userinfo, hostPath string, fileDir string) (baseCmd []string) { + baseCmd = append(baseCmd, "-N") // Append auth user and password to baseCmd auth := used.Username() @@ -249,10 +253,13 @@ func (g *SmbGetter) smbclientBaseCmd(used *url.Userinfo, hostPath string, fileDi if password, ok := used.Password(); ok { auth = auth + "%" + password } - baseCmd = baseCmd + " -U " + auth + baseCmd = append(baseCmd, "-U") + baseCmd = append(baseCmd, auth) } - baseCmd = baseCmd + " " + hostPath + " --directory " + fileDir + baseCmd = append(baseCmd, hostPath) + baseCmd = append(baseCmd, "--directory") + baseCmd = append(baseCmd, fileDir) return baseCmd } @@ -277,9 +284,10 @@ func (g *SmbGetter) findHostAndFilePath(u *url.URL) (string, string, error) { return hostPath, directories[1], nil } -func (g *SmbGetter) isDirectory(baseCmd string, object string) (bool, error) { - objectInfoCmd := baseCmd + " --command " + fmt.Sprintf("'allinfo %s'", object) - output, err := g.runSmbClientCommand(objectInfoCmd, "") +func (g *SmbGetter) isDirectory(args []string, object string) (bool, error) { + args = append(args, "-c") + args = append(args, fmt.Sprintf("allinfo %s", object)) + output, err := g.runSmbClientCommand("", args) if err != nil { return false, err } @@ -289,8 +297,8 @@ func (g *SmbGetter) isDirectory(baseCmd string, object string) (bool, error) { return strings.Contains(output, "attributes: D"), nil } -func (g *SmbGetter) runSmbClientCommand(smbclientCmd string, dst string) (string, error) { - cmd := exec.Command("bash", "-c", smbclientCmd) +func (g *SmbGetter) runSmbClientCommand(dst string, args []string) (string, error) { + cmd := exec.Command("smbclient", args...) if dst != "" { cmd.Dir = dst From 337def6dd93c539066125684bf1442dd5cc34b9f Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 27 Apr 2020 11:50:09 +0200 Subject: [PATCH 071/109] rename err variable --- get_smb.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/get_smb.go b/get_smb.go index 6eaa99ebc..04d0dd800 100644 --- a/get_smb.go +++ b/get_smb.go @@ -27,24 +27,26 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return 0, new(smbPathError) } + var result *multierror.Error // Look in a possible local mount of shared folder path := "/" + u.Host + u.Path if runtime.GOOS == "windows" { path = "/" + path } f := new(FileGetter) - mode, result := f.mode(path) - if result == nil { + mode, err := f.mode(path) + if err == nil { return mode, nil } + result = multierror.Append(result, err) // If not mounted, use smbclient cli to verify mode - mode, err := g.smbClientMode(u) + mode, err = g.smbClientMode(u) if err == nil { return mode, nil } - result = multierror.Append(result, err) + return 0, &smbGeneralError{result} } @@ -89,23 +91,24 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { } } + var result *multierror.Error // First look in a possible local mount of the shared folder path := "/" + req.u.Host + req.u.Path if runtime.GOOS == "windows" { path = "/" + path } f := new(FileGetter) - result := f.get(path, req) - if result == nil { + err := f.get(path, req) + if err == nil { return nil } + result = multierror.Append(result, err) // If not mounted, try downloading the directory content using smbclient cli - err := g.smbclientGet(req) + err = g.smbclientGet(req) if err == nil { return nil } - result = multierror.Append(result, err) if !dstExisted { @@ -167,23 +170,24 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { } } + var result *multierror.Error // First look in a possible local mount of the shared folder path := "/" + req.u.Host + req.u.Path if runtime.GOOS == "windows" { path = "/" + path } f := new(FileGetter) - result := f.getFile(path, req, ctx) - if result == nil { + err := f.getFile(path, req, ctx) + if err == nil { return nil } + result = multierror.Append(result, err) // If not mounted, try downloading the file using smbclient cli - err := g.smbclientGetFile(req) + err = g.smbclientGetFile(req) if err == nil { return nil } - result = multierror.Append(result, err) if !dstExisted { From 0238bda28cb44f47019ab5c4b67d2358ac83e80c Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 27 Apr 2020 13:20:20 +0200 Subject: [PATCH 072/109] User filegetter only for windows when it's available --- .circleci/config.yml | 1 - Makefile | 1 - client_test.go | 40 +++---------------- detect_file.go | 6 +-- detect_smb.go | 14 +++++-- get_file.go | 9 ----- get_smb.go | 83 ++++++++++++++++----------------------- get_smb_test.go | 93 ++++++-------------------------------------- source.go | 4 +- 9 files changed, 66 insertions(+), 185 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 935bf75d4..a887deef7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -82,7 +82,6 @@ jobs: - run: name: run smb getter tests command: | - docker exec -it samba bash -c "echo 'Hello' > /mnt/file.txt && mkdir -p /mnt/subdir && echo 'Hello' > /mnt/subdir/file.txt" docker exec -it gogetter bash -c "env ACC_SMB_TEST=1 go test -v ./... -run=TestSmb_" workflows: diff --git a/Makefile b/Makefile index 69392cdf3..5e58ecbb8 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,6 @@ smbtests-prepare: @docker-compose build @docker-compose up -d @sleep 60 - @docker exec -it samba bash -c "echo 'Hello' > /mnt/file.txt && mkdir -p /mnt/subdir && echo 'Hello' > /mnt/subdir/file.txt" smbtests: @docker cp ./ gogetter:/go-getter/ diff --git a/client_test.go b/client_test.go index 8afdd6824..ec05f29fd 100644 --- a/client_test.go +++ b/client_test.go @@ -11,12 +11,11 @@ func TestSmb_ClientGet(t *testing.T) { smbTestsPreCheck(t) tests := []struct { - name string - rawURL string - mode Mode - file string - mounted bool - fail bool + name string + rawURL string + mode Mode + file string + fail bool }{ { "smb scheme subdir with registered authentication in private share", @@ -24,7 +23,6 @@ func TestSmb_ClientGet(t *testing.T) { ModeDir, "file.txt", false, - false, }, { "smb scheme file with registered authentication with file in private share", @@ -32,7 +30,6 @@ func TestSmb_ClientGet(t *testing.T) { ModeFile, "file.txt", false, - false, }, { "smb scheme file without authentication in public share", @@ -40,23 +37,6 @@ func TestSmb_ClientGet(t *testing.T) { ModeFile, "file.txt", false, - false, - }, - { - "// prefix path local mounted smb shared file", - "//mnt/file.txt", - ModeFile, - "file.txt", - true, - false, - }, - { - "// prefix path local mounted smb shared directory", - "//mnt/subdir", - ModeDir, - "file.txt", - true, - false, }, } @@ -92,16 +72,6 @@ func TestSmb_ClientGet(t *testing.T) { if result.Dst != dst { t.Fatalf("err: expected destination: %s \n actual destination: %s", dst, result.Dst) } - if tt.mounted && tt.mode == ModeDir { - // Verify the destination folder is a symlink to the mounted one - fi, err := os.Lstat(dst) - if err != nil { - t.Fatalf("err: %s", err) - } - if fi.Mode()&os.ModeSymlink == 0 { - t.Fatal("destination is not a symlink") - } - } if tt.mode == ModeDir { dst = filepath.Join(dst, tt.file) } diff --git a/detect_file.go b/detect_file.go index 862c9f2c6..e68a9af25 100644 --- a/detect_file.go +++ b/detect_file.go @@ -5,7 +5,6 @@ import ( "os" "path/filepath" "runtime" - "strings" ) // FileDetector implements Detector to detect file paths. @@ -50,8 +49,9 @@ func (d *FileDetector) Detect(src, pwd string) (string, bool, error) { src = filepath.Join(pwd, src) } - if strings.HasPrefix(src, "//") { - // This is a valid smb path and will be also checked as local file by the SmbGetter + if windowsSmbPath(src) { + // This is a valid smb path for Windows and will be checked in the SmbGetter + // by the file system using the FileGetter, if available. return src, false, nil } diff --git a/detect_smb.go b/detect_smb.go index f27757cf7..348a5b3df 100644 --- a/detect_smb.go +++ b/detect_smb.go @@ -2,10 +2,12 @@ package getter import ( "fmt" + "path/filepath" + "runtime" "strings" ) -// SmbDetector implements Detector to detect smb paths with //. +// SmbDetector implements Detector to detect smb paths with // for Windows OS. type SmbDetector struct{} func (d *SmbDetector) Detect(src, pwd string) (string, bool, error) { @@ -13,9 +15,15 @@ func (d *SmbDetector) Detect(src, pwd string) (string, bool, error) { return "", false, nil } - if strings.HasPrefix(src, "//") { - // This is a valid smb path and will be also checked as local file by the SmbGetter + if windowsSmbPath(src) { + // This is a valid smb path for Windows and will be checked in the SmbGetter + // by the file system using the FileGetter, if available. + src = filepath.ToSlash(src) return fmt.Sprintf("smb:%s", src), true, nil } return "", false, nil } + +func windowsSmbPath(path string) bool { + return runtime.GOOS == "windows" && (strings.HasPrefix(path, "\\\\") || strings.HasPrefix(path, "//")) +} diff --git a/get_file.go b/get_file.go index 48859860b..f11d13ecf 100644 --- a/get_file.go +++ b/get_file.go @@ -19,10 +19,7 @@ func (g *FileGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.RawPath != "" { path = u.RawPath } - return g.mode(path) -} -func (g *FileGetter) mode(path string) (Mode, error) { fi, err := os.Stat(path) if err != nil { return 0, err @@ -41,10 +38,7 @@ func (g *FileGetter) Get(ctx context.Context, req *Request) error { if req.u.RawPath != "" { path = req.u.RawPath } - return g.get(path, req) -} -func (g *FileGetter) get(path string, req *Request) error { // The source path must exist and be a directory to be usable. if fi, err := os.Stat(path); err != nil { return fmt.Errorf("source path error: %s", err) @@ -88,10 +82,7 @@ func (g *FileGetter) GetFile(ctx context.Context, req *Request) error { if req.u.RawPath != "" { path = req.u.RawPath } - return g.getFile(path, req, ctx) -} -func (g *FileGetter) getFile(path string, req *Request, ctx context.Context) error { // The source path must exist and be a file to be usable. if fi, err := os.Stat(path); err != nil { return fmt.Errorf("source path error: %s", err) diff --git a/get_smb.go b/get_smb.go index 04d0dd800..56896cac7 100644 --- a/get_smb.go +++ b/get_smb.go @@ -12,8 +12,6 @@ import ( "runtime" "strings" "syscall" - - "github.com/hashicorp/go-multierror" ) // SmbGetter is a Getter implementation that will download a module from @@ -27,27 +25,22 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return 0, new(smbPathError) } - var result *multierror.Error - // Look in a possible local mount of shared folder - path := "/" + u.Host + u.Path - if runtime.GOOS == "windows" { - path = "/" + path - } - f := new(FileGetter) - mode, err := f.mode(path) - if err == nil { - return mode, nil + // For windows, look for the shared folder withing the file system + if g.client != nil { + fileGetter, ok := g.client.Getters["file"] + if runtime.GOOS == "windows" && ok { + prefix := string(os.PathSeparator) + string(os.PathSeparator) + u.Path = prefix + filepath.Join(u.Host, u.Path) + return fileGetter.Mode(ctx, u) + } } - result = multierror.Append(result, err) - // If not mounted, use smbclient cli to verify mode - mode, err = g.smbClientMode(u) + // Use smbclient cli to verify mode + mode, err := g.smbClientMode(u) if err == nil { return mode, nil } - result = multierror.Append(result, err) - - return 0, &smbGeneralError{result} + return 0, &smbGeneralError{err} } func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { @@ -91,32 +84,28 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { } } - var result *multierror.Error - // First look in a possible local mount of the shared folder - path := "/" + req.u.Host + req.u.Path - if runtime.GOOS == "windows" { - path = "/" + path - } - f := new(FileGetter) - err := f.get(path, req) - if err == nil { - return nil + // For windows, look for the shared folder withing the file system + if g.client != nil { + fileGetter, ok := g.client.Getters["file"] + if runtime.GOOS == "windows" && ok { + prefix := string(os.PathSeparator) + string(os.PathSeparator) + req.u.Path = prefix + filepath.Join(req.u.Host, req.u.Path) + return fileGetter.Get(ctx, req) + } } - result = multierror.Append(result, err) - // If not mounted, try downloading the directory content using smbclient cli - err = g.smbclientGet(req) + // Download the directory content using smbclient cli + err := g.smbclientGet(req) if err == nil { return nil } - result = multierror.Append(result, err) if !dstExisted { // Remove the destination created for smbclient os.Remove(req.Dst) } - return &smbGeneralError{result} + return &smbGeneralError{err} } func (g *SmbGetter) smbclientGet(req *Request) error { @@ -170,32 +159,28 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { } } - var result *multierror.Error - // First look in a possible local mount of the shared folder - path := "/" + req.u.Host + req.u.Path - if runtime.GOOS == "windows" { - path = "/" + path - } - f := new(FileGetter) - err := f.getFile(path, req, ctx) - if err == nil { - return nil + // For windows, look for the shared folder withing the file system + if g.client != nil { + fileGetter, ok := g.client.Getters["file"] + if runtime.GOOS == "windows" && ok { + prefix := string(os.PathSeparator) + string(os.PathSeparator) + req.u.Path = prefix + filepath.Join(req.u.Host, req.u.Path) + return fileGetter.GetFile(ctx, req) + } } - result = multierror.Append(result, err) // If not mounted, try downloading the file using smbclient cli - err = g.smbclientGetFile(req) + err := g.smbclientGetFile(req) if err == nil { return nil } - result = multierror.Append(result, err) if !dstExisted { // Remove the destination created for smbclient os.Remove(req.Dst) } - return &smbGeneralError{result} + return &smbGeneralError{err} } func (g *SmbGetter) smbclientGetFile(req *Request) error { @@ -346,7 +331,7 @@ type smbGeneralError struct { func (e *smbGeneralError) Error() string { if e != nil { - return fmt.Sprintf("one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary). \n err: %s", e.err.Error()) + return fmt.Sprintf("smbclient cli needs to be installed and credentials provided when necessary. \n err: %s", e.err.Error()) } - return "one of the options should be available: \n 1. local mount of the smb shared folder or; \n 2. smbclient cli installed (provice credentials when necessary)." + return "smbclient cli needs to be installed and credentials provided when necessary." } diff --git a/get_smb_test.go b/get_smb_test.go index fd59bf56b..84b1a47d9 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -16,31 +16,27 @@ func TestSmb_GetterGet(t *testing.T) { smbTestsPreCheck(t) tests := []struct { - name string - rawURL string - file string - mounted bool - fail bool + name string + rawURL string + file string + fail bool }{ { "smbclient with registered authentication in private share", "smb://user:password@samba/private/subdir", "file.txt", false, - false, }, { "smbclient with registered authentication with file in private share", "smb://user:password@samba/private/subdir/file.txt", "file.txt", - false, true, }, { "smbclient with only registered username authentication in private share", "smb://user@samba/private/subdir", "file.txt", - false, true, }, { @@ -48,13 +44,11 @@ func TestSmb_GetterGet(t *testing.T) { "smb://username@samba/public/subdir", "file.txt", false, - false, }, { "smbclient without authentication in private share", "smb://samba/private/subdir", "file.txt", - false, true, }, { @@ -62,48 +56,29 @@ func TestSmb_GetterGet(t *testing.T) { "smb://samba/public/subdir", "file.txt", false, - false, - }, - { - "local mounted smb shared file", - "smb://mnt/file.txt", - "file.txt", - true, - true, - }, - { - "local mounted smb shared directory", - "smb://mnt/subdir", - "file.txt", - true, - false, }, { "non existent directory in private share", "smb://user:password@samba/private/invalid", "", - false, true, }, { "non existent directory in public share", "smb://samba/public/invalid", "", - false, true, }, { "no hostname provided", "smb://", "", - false, true, }, { "no filepath provided", "smb://samba", "", - false, true, }, } @@ -123,6 +98,7 @@ func TestSmb_GetterGet(t *testing.T) { } g := new(SmbGetter) + g.SetClient(DefaultClient) err = g.Get(context.Background(), req) fail := err != nil @@ -134,16 +110,6 @@ func TestSmb_GetterGet(t *testing.T) { } if !tt.fail { - if tt.mounted { - // Verify the destination folder is a symlink to the mounted one - fi, err := os.Lstat(dst) - if err != nil { - t.Fatalf("err: %s", err) - } - if fi.Mode()&os.ModeSymlink == 0 { - t.Fatal("destination is not a symlink") - } - } // Verify if the file was successfully downloaded // and exists at the destination folder assertContents(t, filepath.Join(dst, tt.file), "Hello\n") @@ -156,31 +122,27 @@ func TestSmb_GetterGetFile(t *testing.T) { smbTestsPreCheck(t) tests := []struct { - name string - rawURL string - file string - mounted bool - fail bool + name string + rawURL string + file string + fail bool }{ { "smbclient with registered authentication in private share", "smb://user:password@samba/private/file.txt", "file.txt", false, - false, }, { "smbclient with registered authentication and subdirectory in private share", "smb://user:password@samba/private/subdir/file.txt", "file.txt", false, - false, }, { "smbclient with only registered username authentication in private share", "smb://user@samba/private/file.txt", "file.txt", - false, true, }, { @@ -188,76 +150,53 @@ func TestSmb_GetterGetFile(t *testing.T) { "smb://username@samba/public/file.txt", "file.txt", false, - false, }, { "smbclient without authentication in public share", "smb://samba/public/file.txt", "file.txt", false, - false, }, { "smbclient without authentication in private share", "smb://samba/private/file.txt", "file.txt", - false, true, }, { "smbclient get directory in private share", "smb://user:password@samba/private/subdir", "", - false, true, }, { "smbclient get directory in public share", "smb://samba/public/subdir", "", - false, - true, - }, - { - "local mounted smb shared file", - "smb://mnt/file.txt", - "file.txt", - true, - false, - }, - { - "local mounted smb shared directory", - "smb://mnt/subdir", - "", - true, true, }, { "non existent file in private share", "smb://user:password@samba/private/invalidfile.txt", "", - false, true, }, { "non existent file in public share", "smb://samba/public/invalidfile.txt", "", - false, true, }, { "no hostname provided", "smb://", "", - false, true, }, { "no filepath provided", "smb://samba", "", - false, true, }, } @@ -277,6 +216,7 @@ func TestSmb_GetterGetFile(t *testing.T) { } g := new(SmbGetter) + g.SetClient(DefaultClient) err = g.GetFile(context.Background(), req) fail := err != nil @@ -353,18 +293,6 @@ func TestSmb_GetterMode(t *testing.T) { 0, true, }, - { - "local mount modefile for existing file", - "smb://mnt/file.txt", - ModeFile, - false, - }, - { - "local mount modedir for existing directory", - "smb://mnt/subdir", - ModeDir, - false, - }, } for _, tt := range tests { @@ -375,6 +303,7 @@ func TestSmb_GetterMode(t *testing.T) { } g := new(SmbGetter) + g.SetClient(DefaultClient) mode, err := g.Mode(context.Background(), url) fail := err != nil diff --git a/source.go b/source.go index 74e60dd37..bd0ddc1b2 100644 --- a/source.go +++ b/source.go @@ -15,8 +15,8 @@ import ( // proto://dom.com/path//path2?q=p => proto://dom.com/path?q=p, "path2" // func SourceDirSubdir(src string) (string, string) { - if strings.HasPrefix(src, "//") { - // This is valid for smb path + if windowsSmbPath(src) { + // This is valid for smb path for Windows return src, "" } From ebf89a3099a4c859203b732488d0aab92375d715 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 27 Apr 2020 14:19:19 +0200 Subject: [PATCH 073/109] update readme --- README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 982554756..00df70173 100644 --- a/README.md +++ b/README.md @@ -364,22 +364,30 @@ The tests for `get_gcs.go` require you to have GCP credentials set in your envir ### SMB (smb) -The SMB getter will try to get files from samba when a url is prefixed with `smb://`, for example `smb://foo/bar/dir`. - -The go-getter will try for a local mount of the path such as `/foo/bar/dir` (Unix) or `//foo/bar/dir` (Windows), -and will fallback to using the [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) command to download the file/dir. +On Unix, the SMB getter will try to get files from samba using the [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) when a url is prefixed with `smb://`, for example `smb://foo/bar/dir`. +For a local mount, the FileGetter must be used instead. +⚠️ The [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) needs to be installed for this to work on Unix. + +On Windows, the SMB getter will try to get files using the windows file system when the url is prefixed with `smb://`, `//`, or `\\`. This will be done by using the existing FileGetter. +⚠️ A FileGetter is necessary for this to work on Windows. -⚠️ On Unix when the smb share is not mounted, [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) needs to be installed for this to work. +The following examples work for Windows and Unix: +- smb://host/shared/dir (downloads directory content) +- smb://host/shared/dir/file (downloads file) -Some examples for addressing the scheme: +The following examples work for Unix: - smb://username:password@host/shared/dir (downloads directory content) - smb://username@host/shared/dir -- smb://host/shared/dir - smb://username:password@host/shared/dir/file (downloads file) - smb://username@host/shared/dir/file -- smb://host/shared/dir/file + +⚠️ The above examples also work on Windows but the authentication is not use to access the file system. + +These examples only work for Windows: - //host/shared/dir (downloads directory content) - //host/shared/dir/file (downloads file) +- \\\host\shared\dir (downloads directory content) +- \\\host\shared\dir\file (downloads file) #### SMB Testing From ed2ff252778830b962c8b18eea773f70a58696d4 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 27 Apr 2020 14:32:54 +0200 Subject: [PATCH 074/109] fix comment description --- get_smb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get_smb.go b/get_smb.go index 56896cac7..852b9feef 100644 --- a/get_smb.go +++ b/get_smb.go @@ -15,7 +15,7 @@ import ( ) // SmbGetter is a Getter implementation that will download a module from -// a shared folder using smbclient cli or looking for local mount. +// a shared folder using smbclient cli for Unix and local file system for Windows. type SmbGetter struct { getter } From d4b40f13942d875927dc42faf497e6f1d1ef06ce Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 30 Apr 2020 18:06:08 +0200 Subject: [PATCH 075/109] big getter refactoring --- client.go | 381 +++++++++++++++++++++------------------ client_option.go | 8 - detect.go | 111 ++++-------- detect_bitbucket.go | 66 ------- detect_bitbucket_test.go | 13 +- detect_file.go | 73 -------- detect_file_test.go | 33 ++-- detect_file_unix_test.go | 2 +- detect_gcs.go | 43 ----- detect_gcs_test.go | 8 +- detect_git.go | 26 --- detect_git_test.go | 29 +-- detect_github.go | 47 ----- detect_github_test.go | 12 +- detect_s3.go | 61 ------- detect_s3_test.go | 28 +-- detect_smb.go | 29 --- detect_test.go | 44 +++-- get.go | 32 ++-- get_base.go | 9 - get_file.go | 69 ++++++- get_gcs.go | 43 ++++- get_git.go | 112 +++++++++++- get_git_test.go | 27 +-- get_hg.go | 33 +++- get_http.go | 15 +- get_mock.go | 10 +- get_s3.go | 61 ++++++- get_smb.go | 51 ++---- get_smb_test.go | 3 - get_smb_win.go | 91 ++++++++++ get_test.go | 18 +- 32 files changed, 805 insertions(+), 783 deletions(-) delete mode 100644 detect_bitbucket.go delete mode 100644 detect_file.go delete mode 100644 detect_gcs.go delete mode 100644 detect_git.go delete mode 100644 detect_github.go delete mode 100644 detect_s3.go delete mode 100644 detect_smb.go delete mode 100644 get_base.go create mode 100644 get_smb_win.go diff --git a/client.go b/client.go index a6a1bf2bd..6a5e6564b 100644 --- a/client.go +++ b/client.go @@ -19,18 +19,13 @@ import ( // Using a client directly allows more fine-grained control over how downloading // is done, as well as customizing the protocols supported. type Client struct { - - // Detectors is the list of detectors that are tried on the source. - // If this is nil, then the default Detectors will be used. - Detectors []Detector - // Decompressors is the map of decompressors supported by this client. // If this is nil, then the default value is the Decompressors global. Decompressors map[string]Decompressor // Getters is the map of protocols supported by this client. If this // is nil, then the default Getters variable will be used. - Getters map[string]Getter + Getters []Getter } // GetResult is the result of a Client.Get @@ -50,218 +45,250 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { req.Mode = ModeAny } - var err error - req.Src, err = Detect(req.Src, req.Pwd, c.Detectors) - if err != nil { - return nil, err - } - - var force string - // Determine if we have a forced protocol, i.e. "git::http://..." - force, req.Src = getForcedGetter(req.Src) - - // If there is a subdir component, then we download the root separately - // and then copy over the proper subdir. - var realDst, subDir string - req.Src, subDir = SourceDirSubdir(req.Src) - if subDir != "" { - td, tdcloser, err := safetemp.Dir("", "getter") + // Loop over Getters in priority order. + // Some urls can be supported by more than one Getter and we want to make sure + // the best getter option will try to download first + for _, g := range c.Getters { + src, ok, err := Detect(req.Src, req.Pwd, g) if err != nil { return nil, err } - defer tdcloser.Close() - - realDst = req.Dst - req.Dst = td - } - - req.u, err = urlhelper.Parse(req.Src) - if err != nil { - return nil, err - } - if force == "" { - force = req.u.Scheme - } - - g, ok := c.Getters[force] - if !ok { - return nil, fmt.Errorf( - "download not supported for scheme '%s'", force) - } + if !ok { + continue + } + req.Src = src + + // If there is a subdir component, then we download the root separately + // and then copy over the proper subdir. + var realDst, subDir string + req.Src, subDir = SourceDirSubdir(req.Src) + if subDir != "" { + // TODO @sylviamoss different dirs for different getters + td, tdcloser, err := safetemp.Dir("", "getter") + if err != nil { + return nil, err + } + defer tdcloser.Close() - // We have magic query parameters that we use to signal different features - q := req.u.Query() + realDst = req.Dst + req.Dst = td + } - // Determine if we have an archive type - archiveV := q.Get("archive") - if archiveV != "" { - // Delete the paramter since it is a magic parameter we don't - // want to pass on to the Getter - q.Del("archive") - req.u.RawQuery = q.Encode() + req.u, err = urlhelper.Parse(req.Src) + if err != nil { + //return nil, err + // TODO @sylviamoss save error + continue + } - // If we can parse the value as a bool and it is false, then - // set the archive to "-" which should never map to a decompressor - if b, err := strconv.ParseBool(archiveV); err == nil && !b { - archiveV = "-" + // We have magic query parameters that we use to signal different features + q := req.u.Query() + + // Determine if we have an archive type + archiveV := q.Get("archive") + if archiveV != "" { + // Delete the paramter since it is a magic parameter we don't + // want to pass on to the Getter + q.Del("archive") + req.u.RawQuery = q.Encode() + + // If we can parse the value as a bool and it is false, then + // set the archive to "-" which should never map to a decompressor + if b, err := strconv.ParseBool(archiveV); err == nil && !b { + archiveV = "-" + } + } else { + // We don't appear to... but is it part of the filename? + matchingLen := 0 + for k := range c.Decompressors { + if strings.HasSuffix(req.u.Path, "."+k) && len(k) > matchingLen { + archiveV = k + matchingLen = len(k) + } + } } - } - if archiveV == "" { - // We don't appear to... but is it part of the filename? - matchingLen := 0 - for k := range c.Decompressors { - if strings.HasSuffix(req.u.Path, "."+k) && len(k) > matchingLen { - archiveV = k - matchingLen = len(k) + + // If we have a decompressor, then we need to change the destination + // to download to a temporary path. We unarchive this into the final, + // real path. + var decompressDst string + var decompressDir bool + decompressor := c.Decompressors[archiveV] + if decompressor != nil { + // Create a temporary directory to store our archive. We delete + // this at the end of everything. + td, err := ioutil.TempDir("", "getter") + if err != nil { + return nil, fmt.Errorf( + "Error creating temporary directory for archive: %s", err) } + defer os.RemoveAll(td) + + // Swap the download directory to be our temporary path and + // store the old values. + decompressDst = req.Dst + decompressDir = req.Mode != ModeFile + req.Dst = filepath.Join(td, "archive") + req.Mode = ModeFile } - } - // If we have a decompressor, then we need to change the destination - // to download to a temporary path. We unarchive this into the final, - // real path. - var decompressDst string - var decompressDir bool - decompressor := c.Decompressors[archiveV] - if decompressor != nil { - // Create a temporary directory to store our archive. We delete - // this at the end of everything. - td, err := ioutil.TempDir("", "getter") + // Determine checksum if we have one + checksum, err := c.extractChecksum(ctx, req.u) if err != nil { - return nil, fmt.Errorf( - "Error creating temporary directory for archive: %s", err) + return nil, fmt.Errorf("invalid checksum: %s", err) } - defer os.RemoveAll(td) - - // Swap the download directory to be our temporary path and - // store the old values. - decompressDst = req.Dst - decompressDir = req.Mode != ModeFile - req.Dst = filepath.Join(td, "archive") - req.Mode = ModeFile - } - // Determine checksum if we have one - checksum, err := c.extractChecksum(ctx, req.u) - if err != nil { - return nil, fmt.Errorf("invalid checksum: %s", err) - } + // Delete the query parameter if we have it. + q.Del("checksum") + req.u.RawQuery = q.Encode() - // Delete the query parameter if we have it. - q.Del("checksum") - req.u.RawQuery = q.Encode() + if req.Mode == ModeAny { + // Ask the getter which client mode to use + req.Mode, err = g.Mode(ctx, req.u) + if err != nil { + //return nil, err + // TODO @sylviamoss save error + continue + } - if req.Mode == ModeAny { - // Ask the getter which client mode to use - req.Mode, err = g.Mode(ctx, req.u) - if err != nil { - return nil, err - } + // Destination is the base name of the URL path in "any" mode when + // a file source is detected. + if req.Mode == ModeFile { + filename := filepath.Base(req.u.Path) - // Destination is the base name of the URL path in "any" mode when - // a file source is detected. - if req.Mode == ModeFile { - filename := filepath.Base(req.u.Path) + // Determine if we have a custom file name + if v := q.Get("filename"); v != "" { + // Delete the query parameter if we have it. + q.Del("filename") + req.u.RawQuery = q.Encode() - // Determine if we have a custom file name - if v := q.Get("filename"); v != "" { - // Delete the query parameter if we have it. - q.Del("filename") - req.u.RawQuery = q.Encode() + filename = v + } - filename = v + req.Dst = filepath.Join(req.Dst, filename) } - - req.Dst = filepath.Join(req.Dst, filename) } - } - // If we're not downloading a directory, then just download the file - // and return. - if req.Mode == ModeFile { - getFile := true - if checksum != nil { - if err := checksum.checksum(req.Dst); err == nil { - // don't get the file if the checksum of dst is correct - getFile = false + // If we're not downloading a directory, then just download the file + // and return. + if req.Mode == ModeFile { + getFile := true + if checksum != nil { + if err := checksum.checksum(req.Dst); err == nil { + // don't get the file if the checksum of dst is correct + getFile = false + } } - } - if getFile { - err := g.GetFile(ctx, req) - if err != nil { - return nil, err + if getFile { + err := g.GetFile(ctx, req) + if err != nil { + //return nil, err + // TODO @sylviamoss save error + continue + } + + if checksum != nil { + if err := checksum.checksum(req.Dst); err != nil { + return nil, err + } + } } - if checksum != nil { - if err := checksum.checksum(req.Dst); err != nil { + if decompressor != nil { + // We have a decompressor, so decompress the current destination + // into the final destination with the proper mode. + err := decompressor.Decompress(decompressDst, req.Dst, decompressDir) + if err != nil { return nil, err } + + // Swap the information back + req.Dst = decompressDst + if decompressDir { + req.Mode = ModeAny + } else { + req.Mode = ModeFile + } + } + + // We check the dir value again because it can be switched back + // if we were unarchiving. If we're still only Get-ing a file, then + // we're done. + if req.Mode == ModeFile { + return &GetResult{req.Dst}, nil } } - if decompressor != nil { - // We have a decompressor, so decompress the current destination - // into the final destination with the proper mode. - err := decompressor.Decompress(decompressDst, req.Dst, decompressDir) + // If we're at this point we're either downloading a directory or we've + // downloaded and unarchived a directory and we're just checking subdir. + // In the case we have a decompressor we don't Get because it was Get + // above. + if decompressor == nil { + // If we're getting a directory, then this is an error. You cannot + // checksum a directory. TODO: test + if checksum != nil { + return nil, fmt.Errorf( + "checksum cannot be specified for directory download") + } + + // We're downloading a directory, which might require a bit more work + // if we're specifying a subdir. + err := g.Get(ctx, req) if err != nil { + //err = fmt.Errorf("error downloading '%s': %s", req.Src, err) + //return nil, err + // TODO @sylviamoss save error + continue + } + } + + // If we have a subdir, copy that over + if subDir != "" { + if err := os.RemoveAll(realDst); err != nil { + return nil, err + } + if err := os.MkdirAll(realDst, 0755); err != nil { return nil, err } - // Swap the information back - req.Dst = decompressDst - if decompressDir { - req.Mode = ModeAny - } else { - req.Mode = ModeFile + // Process any globs + subDir, err := SubdirGlob(req.Dst, subDir) + if err != nil { + return nil, err } - } - // We check the dir value again because it can be switched back - // if we were unarchiving. If we're still only Get-ing a file, then - // we're done. - if req.Mode == ModeFile { - return &GetResult{req.Dst}, nil + return &GetResult{realDst}, copyDir(ctx, realDst, subDir, false) } + return &GetResult{req.Dst}, nil } - // If we're at this point we're either downloading a directory or we've - // downloaded and unarchived a directory and we're just checking subdir. - // In the case we have a decompressor we don't Get because it was Get - // above. - if decompressor == nil { - // If we're getting a directory, then this is an error. You cannot - // checksum a directory. TODO: test - if checksum != nil { - return nil, fmt.Errorf( - "checksum cannot be specified for directory download") - } + return nil, fmt.Errorf("failed to download %s", req.Src) +} - // We're downloading a directory, which might require a bit more work - // if we're specifying a subdir. - err := g.Get(ctx, req) - if err != nil { - err = fmt.Errorf("error downloading '%s': %s", req.Src, err) - return nil, err - } - } +func (c *Client) checkArchive(req *Request) string { + q := req.u.Query() + archiveV := q.Get("archive") + if archiveV != "" { + // Delete the paramter since it is a magic parameter we don't + // want to pass on to the Getter + q.Del("archive") + req.u.RawQuery = q.Encode() - // If we have a subdir, copy that over - if subDir != "" { - if err := os.RemoveAll(realDst); err != nil { - return nil, err - } - if err := os.MkdirAll(realDst, 0755); err != nil { - return nil, err + // If we can parse the value as a bool and it is false, then + // set the archive to "-" which should never map to a decompressor + if b, err := strconv.ParseBool(archiveV); err == nil && !b { + archiveV = "-" } - - // Process any globs - subDir, err := SubdirGlob(req.Dst, subDir) - if err != nil { - return nil, err + } + if archiveV == "" { + // We don't appear to... but is it part of the filename? + matchingLen := 0 + for k := range c.Decompressors { + if strings.HasSuffix(req.u.Path, "."+k) && len(k) > matchingLen { + archiveV = k + matchingLen = len(k) + } } - - return &GetResult{realDst}, copyDir(ctx, realDst, subDir, false) } - - return &GetResult{req.Dst}, nil + return archiveV } diff --git a/client_option.go b/client_option.go index 567f3c8ac..c17f73fe9 100644 --- a/client_option.go +++ b/client_option.go @@ -6,17 +6,9 @@ func (c *Client) configure() error { if c.Decompressors == nil { c.Decompressors = Decompressors } - // Default detector values - if c.Detectors == nil { - c.Detectors = Detectors - } // Default getter values if c.Getters == nil { c.Getters = Getters } - - for _, getter := range c.Getters { - getter.SetClient(c) - } return nil } diff --git a/detect.go b/detect.go index c82385e5e..2aa40e91c 100644 --- a/detect.go +++ b/detect.go @@ -7,31 +7,6 @@ import ( "github.com/hashicorp/go-getter/helper/url" ) -// Detector defines the interface that an invalid URL or a URL with a blank -// scheme is passed through in order to determine if its shorthand for -// something else well-known. -type Detector interface { - // Detect will detect whether the string matches a known pattern to - // turn it into a proper URL. - Detect(string, string) (string, bool, error) -} - -// Detectors is the list of detectors that are tried on an invalid URL. -// This is also the order they're tried (index 0 is first). -var Detectors []Detector - -func init() { - Detectors = []Detector{ - new(GitHubDetector), - new(GitDetector), - new(BitBucketDetector), - new(S3Detector), - new(GCSDetector), - new(FileDetector), - new(SmbDetector), - } -} - // Detect turns a source string into another source string if it is // detected to be of a known pattern. // @@ -41,66 +16,60 @@ func init() { // // This is safe to be called with an already valid source string: Detect // will just return it. -func Detect(src string, pwd string, ds []Detector) (string, error) { +func Detect(src string, pwd string, g Getter) (string, bool, error) { getForce, getSrc := getForcedGetter(src) - // Separate out the subdir if there is one, we don't pass that to detect - getSrc, subDir := SourceDirSubdir(getSrc) - - u, err := url.Parse(getSrc) - if err == nil && u.Scheme != "" { - // Valid URL - return src, nil + if getForce != "" && !g.ValidScheme(getForce){ + // Another getter is being forced + return "", false, nil } - for _, d := range ds { - result, ok, err := d.Detect(getSrc, pwd) - if err != nil { - return "", err - } - if !ok { - continue - } + isForcedGetter := getForce != "" && g.ValidScheme(getForce) - var detectForce string - detectForce, result = getForcedGetter(result) - result, detectSubdir := SourceDirSubdir(result) + // Separate out the subdir if there is one, we don't pass that to detect + getSrc, subDir := SourceDirSubdir(getSrc) - // If we have a subdir from the detection, then prepend it to our - // requested subdir. - if detectSubdir != "" { - if subDir != "" { - subDir = filepath.Join(detectSubdir, subDir) - } else { - subDir = detectSubdir - } + if !isForcedGetter { + u, err := url.Parse(getSrc) + if err == nil && u.Scheme != "" { + return getSrc, g.ValidScheme(u.Scheme), nil } + } - if subDir != "" { - u, err := url.Parse(result) - if err != nil { - return "", fmt.Errorf("Error parsing URL: %s", err) - } - u.Path += "//" + subDir + result, ok, err := g.Detect(getSrc, pwd) + if err != nil { + return "", false, err + } + if !ok { + // If is this is the forced getter we return true even the detection wasn't valid + return getSrc, isForcedGetter, nil + } - // a subdir may contain wildcards, but in order to support them we - // have to ensure the path isn't escaped. - u.RawPath = u.Path + result, detectSubdir := SourceDirSubdir(result) - result = u.String() + // If we have a subdir from the detection, then prepend it to our + // requested subdir. + if detectSubdir != "" { + if subDir != "" { + subDir = filepath.Join(detectSubdir, subDir) + } else { + subDir = detectSubdir } + } - // Preserve the forced getter if it exists. We try to use the - // original set force first, followed by any force set by the - // detector. - if getForce != "" { - result = fmt.Sprintf("%s::%s", getForce, result) - } else if detectForce != "" { - result = fmt.Sprintf("%s::%s", detectForce, result) + if subDir != "" { + u, err := url.Parse(result) + if err != nil { + return "", false, fmt.Errorf("Error parsing URL: %s", err) } + u.Path += "//" + subDir + + // a subdir may contain wildcards, but in order to support them we + // have to ensure the path isn't escaped. + u.RawPath = u.Path - return result, nil + result = u.String() } - return "", fmt.Errorf("invalid source string: %s", src) + return result, true, nil } diff --git a/detect_bitbucket.go b/detect_bitbucket.go deleted file mode 100644 index 19047eb19..000000000 --- a/detect_bitbucket.go +++ /dev/null @@ -1,66 +0,0 @@ -package getter - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" -) - -// BitBucketDetector implements Detector to detect BitBucket URLs and turn -// them into URLs that the Git or Hg Getter can understand. -type BitBucketDetector struct{} - -func (d *BitBucketDetector) Detect(src, _ string) (string, bool, error) { - if len(src) == 0 { - return "", false, nil - } - - if strings.HasPrefix(src, "bitbucket.org/") { - return d.detectHTTP(src) - } - - return "", false, nil -} - -func (d *BitBucketDetector) detectHTTP(src string) (string, bool, error) { - u, err := url.Parse("https://" + src) - if err != nil { - return "", true, fmt.Errorf("error parsing BitBucket URL: %s", err) - } - - // We need to get info on this BitBucket repository to determine whether - // it is Git or Hg. - var info struct { - SCM string `json:"scm"` - } - infoUrl := "https://api.bitbucket.org/2.0/repositories" + u.Path - resp, err := http.Get(infoUrl) - if err != nil { - return "", true, fmt.Errorf("error looking up BitBucket URL: %s", err) - } - if resp.StatusCode == 403 { - // A private repo - return "", true, fmt.Errorf( - "shorthand BitBucket URL can't be used for private repos, " + - "please use a full URL") - } - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&info); err != nil { - return "", true, fmt.Errorf("error looking up BitBucket URL: %s", err) - } - - switch info.SCM { - case "git": - if !strings.HasSuffix(u.Path, ".git") { - u.Path += ".git" - } - - return "git::" + u.String(), true, nil - case "hg": - return "hg::" + u.String(), true, nil - default: - return "", true, fmt.Errorf("unknown BitBucket SCM type: %s", info.SCM) - } -} diff --git a/detect_bitbucket_test.go b/detect_bitbucket_test.go index 202c93256..e9c4c256f 100644 --- a/detect_bitbucket_test.go +++ b/detect_bitbucket_test.go @@ -19,30 +19,33 @@ func TestBitBucketDetector(t *testing.T) { cases := []struct { Input string Output string + g Getter }{ // HTTP { "bitbucket.org/hashicorp/tf-test-git", - "git::https://bitbucket.org/hashicorp/tf-test-git.git", + "https://bitbucket.org/hashicorp/tf-test-git.git", + new(GitGetter), }, { "bitbucket.org/hashicorp/tf-test-git.git", - "git::https://bitbucket.org/hashicorp/tf-test-git.git", + "https://bitbucket.org/hashicorp/tf-test-git.git", + new(GitGetter), }, { "bitbucket.org/hashicorp/tf-test-hg", - "hg::https://bitbucket.org/hashicorp/tf-test-hg", + "https://bitbucket.org/hashicorp/tf-test-hg", + new(HgGetter), }, } pwd := "/pwd" - f := new(BitBucketDetector) for i, tc := range cases { var err error for i := 0; i < 3; i++ { var output string var ok bool - output, ok, err = f.Detect(tc.Input, pwd) + output, ok, err = tc.g.Detect(tc.Input, pwd) if err != nil { if strings.Contains(err.Error(), "invalid character") { continue diff --git a/detect_file.go b/detect_file.go deleted file mode 100644 index e68a9af25..000000000 --- a/detect_file.go +++ /dev/null @@ -1,73 +0,0 @@ -package getter - -import ( - "fmt" - "os" - "path/filepath" - "runtime" -) - -// FileDetector implements Detector to detect file paths. -type FileDetector struct{} - -func (d *FileDetector) Detect(src, pwd string) (string, bool, error) { - if len(src) == 0 { - return "", false, nil - } - - if !filepath.IsAbs(src) { - if pwd == "" { - return "", true, fmt.Errorf( - "relative paths require a module with a pwd") - } - - // Stat the pwd to determine if its a symbolic link. If it is, - // then the pwd becomes the original directory. Otherwise, - // `filepath.Join` below does some weird stuff. - // - // We just ignore if the pwd doesn't exist. That error will be - // caught later when we try to use the URL. - if fi, err := os.Lstat(pwd); !os.IsNotExist(err) { - if err != nil { - return "", true, err - } - if fi.Mode()&os.ModeSymlink != 0 { - pwd, err = filepath.EvalSymlinks(pwd) - if err != nil { - return "", true, err - } - - // The symlink itself might be a relative path, so we have to - // resolve this to have a correctly rooted URL. - pwd, err = filepath.Abs(pwd) - if err != nil { - return "", true, err - } - } - } - - src = filepath.Join(pwd, src) - } - - if windowsSmbPath(src) { - // This is a valid smb path for Windows and will be checked in the SmbGetter - // by the file system using the FileGetter, if available. - return src, false, nil - } - - return fmtFileURL(src), true, nil -} - -func fmtFileURL(path string) string { - if runtime.GOOS == "windows" { - // Make sure we're using "/" on Windows. URLs are "/"-based. - path = filepath.ToSlash(path) - return fmt.Sprintf("file://%s", path) - } - - // Make sure that we don't start with "/" since we add that below. - if path[0] == '/' { - path = path[1:] - } - return fmt.Sprintf("file:///%s", path) -} diff --git a/detect_file_test.go b/detect_file_test.go index 0f7a055c9..0c7fc836e 100644 --- a/detect_file_test.go +++ b/detect_file_test.go @@ -5,33 +5,32 @@ import ( "os" "path/filepath" "runtime" - "strings" "testing" ) type fileTest struct { in, pwd, out string - err bool + symlink, err bool } var fileTests = []fileTest{ - {"./foo", "/pwd", "file:///pwd/foo", false}, - {"./foo?foo=bar", "/pwd", "file:///pwd/foo?foo=bar", false}, - {"foo", "/pwd", "file:///pwd/foo", false}, + {"./foo", "/pwd", "/pwd/foo", false,false}, + {"./foo?foo=bar", "/pwd", "/pwd/foo?foo=bar", false,false}, + {"foo", "/pwd", "/pwd/foo", false,false}, } var unixFileTests = []fileTest{ {"./foo", "testdata/detect-file-symlink-pwd/syml/pwd", - "testdata/detect-file-symlink-pwd/real/foo", false}, + "testdata/detect-file-symlink-pwd/real/foo", true,false}, - {"/foo", "/pwd", "file:///foo", false}, - {"/foo?bar=baz", "/pwd", "file:///foo?bar=baz", false}, + {"/foo", "/pwd", "/foo", false,false}, + {"/foo?bar=baz", "/pwd", "/foo?bar=baz", false,false}, } var winFileTests = []fileTest{ - {"/foo", "/pwd", "file:///pwd/foo", false}, - {`C:\`, `/pwd`, `file://C:/`, false}, - {`C:\?bar=baz`, `/pwd`, `file://C:/?bar=baz`, false}, + {"/foo", "/pwd", "/pwd/foo", false,false}, + {`C:\`, `/pwd`, `C:/`, false,false}, + {`C:\?bar=baz`, `/pwd`, `C:/?bar=baz`, false,false}, } func TestFileDetector(t *testing.T) { @@ -51,7 +50,7 @@ func TestFileDetector(t *testing.T) { t.Fatalf("err: %s", err) } - f := new(FileDetector) + f := new(FileGetter) for i, tc := range fileTests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { pwd := tc.pwd @@ -65,8 +64,8 @@ func TestFileDetector(t *testing.T) { } expected := tc.out - if !strings.HasPrefix(expected, "file://") { - expected = "file://" + filepath.Join(pwdRoot, expected) + if tc.symlink { + expected = filepath.Join(pwdRoot, expected) } if out != expected { @@ -83,12 +82,12 @@ var noPwdFileTests = []fileTest{ } var noPwdUnixFileTests = []fileTest{ - {in: "/foo", pwd: "", out: "file:///foo", err: false}, + {in: "/foo", pwd: "", out: "/foo", err: false}, } var noPwdWinFileTests = []fileTest{ {in: "/foo", pwd: "", out: "", err: true}, - {in: `C:\`, pwd: ``, out: `file://C:/`, err: false}, + {in: `C:\`, pwd: ``, out: `C:/`, err: false}, } func TestFileDetector_noPwd(t *testing.T) { @@ -98,7 +97,7 @@ func TestFileDetector_noPwd(t *testing.T) { noPwdFileTests = append(noPwdFileTests, noPwdUnixFileTests...) } - f := new(FileDetector) + f := new(FileGetter) for i, tc := range noPwdFileTests { out, ok, err := f.Detect(tc.in, tc.pwd) if err != nil != tc.err { diff --git a/detect_file_unix_test.go b/detect_file_unix_test.go index 657f1a41c..0d3ebf6df 100644 --- a/detect_file_unix_test.go +++ b/detect_file_unix_test.go @@ -55,7 +55,7 @@ func TestFileDetector_relativeSymlink(t *testing.T) { // if detech doesn't fully resolve the pwd symlink, the output will be the // invalid path: "file:///../modules/foo" - f := new(FileDetector) + f := new(FileGetter) out, ok, err := f.Detect("../modules/foo", "./linkedPWD") if err != nil { t.Fatalf("err: %v", err) diff --git a/detect_gcs.go b/detect_gcs.go deleted file mode 100644 index 11363737c..000000000 --- a/detect_gcs.go +++ /dev/null @@ -1,43 +0,0 @@ -package getter - -import ( - "fmt" - "net/url" - "strings" -) - -// GCSDetector implements Detector to detect GCS URLs and turn -// them into URLs that the GCSGetter can understand. -type GCSDetector struct{} - -func (d *GCSDetector) Detect(src, _ string) (string, bool, error) { - if len(src) == 0 { - return "", false, nil - } - - if strings.Contains(src, "googleapis.com/") { - return d.detectHTTP(src) - } - - return "", false, nil -} - -func (d *GCSDetector) detectHTTP(src string) (string, bool, error) { - - parts := strings.Split(src, "/") - if len(parts) < 5 { - return "", false, fmt.Errorf( - "URL is not a valid GCS URL") - } - version := parts[2] - bucket := parts[3] - object := strings.Join(parts[4:], "/") - - url, err := url.Parse(fmt.Sprintf("https://www.googleapis.com/storage/%s/%s/%s", - version, bucket, object)) - if err != nil { - return "", false, fmt.Errorf("error parsing GCS URL: %s", err) - } - - return "gcs::" + url.String(), true, nil -} diff --git a/detect_gcs_test.go b/detect_gcs_test.go index 94e1ec42b..8f78e8a2a 100644 --- a/detect_gcs_test.go +++ b/detect_gcs_test.go @@ -11,20 +11,20 @@ func TestGCSDetector(t *testing.T) { }{ { "www.googleapis.com/storage/v1/bucket/foo", - "gcs::https://www.googleapis.com/storage/v1/bucket/foo", + "https://www.googleapis.com/storage/v1/bucket/foo", }, { "www.googleapis.com/storage/v1/bucket/foo/bar", - "gcs::https://www.googleapis.com/storage/v1/bucket/foo/bar", + "https://www.googleapis.com/storage/v1/bucket/foo/bar", }, { "www.googleapis.com/storage/v1/foo/bar.baz", - "gcs::https://www.googleapis.com/storage/v1/foo/bar.baz", + "https://www.googleapis.com/storage/v1/foo/bar.baz", }, } pwd := "/pwd" - f := new(GCSDetector) + f := new(GCSGetter) for i, tc := range cases { output, ok, err := f.Detect(tc.Input, pwd) if err != nil { diff --git a/detect_git.go b/detect_git.go deleted file mode 100644 index eeb8a04c5..000000000 --- a/detect_git.go +++ /dev/null @@ -1,26 +0,0 @@ -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 -} diff --git a/detect_git_test.go b/detect_git_test.go index a71dde2c3..99af4141f 100644 --- a/detect_git_test.go +++ b/detect_git_test.go @@ -11,56 +11,61 @@ func TestGitDetector(t *testing.T) { }{ { "git@github.com:hashicorp/foo.git", - "git::ssh://git@github.com/hashicorp/foo.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", + "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", + "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", + "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", + "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", + "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", + "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", + "ssh://git@github.xyz.com/org/project.git//module/a?ref=test-branch", }, { // Already in the canonical form, so no rewriting required // When the ssh: protocol is used explicitly, we recognize it as // URL form rather than SCP-like form, so the part after the colon // is a port number, not part of the path. + + // TODO @sylviamoss rewrite comment + // No need to set git scheme anymore "git::ssh://git@git.example.com:2222/hashicorp/foo.git", - "git::ssh://git@git.example.com:2222/hashicorp/foo.git", + "ssh://git@git.example.com:2222/hashicorp/foo.git", }, } pwd := "/pwd" - f := new(GitDetector) - ds := []Detector{f} for _, tc := range cases { t.Run(tc.Input, func(t *testing.T) { - output, err := Detect(tc.Input, pwd, ds) + output, ok, err := Detect(tc.Input, pwd, new(GitGetter)) if err != nil { t.Fatalf("unexpected error: %s", err) } + if !ok { + t.Fatalf("%s url expected to valid", tc.Input) + } + if output != tc.Output { t.Errorf("wrong result\ninput: %s\ngot: %s\nwant: %s", tc.Input, output, tc.Output) } diff --git a/detect_github.go b/detect_github.go deleted file mode 100644 index 4bf4daf23..000000000 --- a/detect_github.go +++ /dev/null @@ -1,47 +0,0 @@ -package getter - -import ( - "fmt" - "net/url" - "strings" -) - -// GitHubDetector implements Detector to detect GitHub URLs and turn -// them into URLs that the Git Getter can understand. -type GitHubDetector struct{} - -func (d *GitHubDetector) Detect(src, _ string) (string, bool, error) { - if len(src) == 0 { - return "", false, nil - } - - if strings.HasPrefix(src, "github.com/") { - return d.detectHTTP(src) - } - - return "", false, nil -} - -func (d *GitHubDetector) detectHTTP(src string) (string, bool, error) { - parts := strings.Split(src, "/") - if len(parts) < 3 { - return "", false, fmt.Errorf( - "GitHub URLs should be github.com/username/repo") - } - - urlStr := fmt.Sprintf("https://%s", strings.Join(parts[:3], "/")) - url, err := url.Parse(urlStr) - if err != nil { - return "", true, fmt.Errorf("error parsing GitHub URL: %s", err) - } - - if !strings.HasSuffix(url.Path, ".git") { - url.Path += ".git" - } - - if len(parts) > 3 { - url.Path += "//" + strings.Join(parts[3:], "/") - } - - return "git::" + url.String(), true, nil -} diff --git a/detect_github_test.go b/detect_github_test.go index 70f1c8329..427e175ad 100644 --- a/detect_github_test.go +++ b/detect_github_test.go @@ -10,24 +10,24 @@ func TestGitHubDetector(t *testing.T) { Output string }{ // HTTP - {"github.com/hashicorp/foo", "git::https://github.com/hashicorp/foo.git"}, - {"github.com/hashicorp/foo.git", "git::https://github.com/hashicorp/foo.git"}, + {"github.com/hashicorp/foo", "https://github.com/hashicorp/foo.git"}, + {"github.com/hashicorp/foo.git", "https://github.com/hashicorp/foo.git"}, { "github.com/hashicorp/foo/bar", - "git::https://github.com/hashicorp/foo.git//bar", + "https://github.com/hashicorp/foo.git//bar", }, { "github.com/hashicorp/foo?foo=bar", - "git::https://github.com/hashicorp/foo.git?foo=bar", + "https://github.com/hashicorp/foo.git?foo=bar", }, { "github.com/hashicorp/foo.git?foo=bar", - "git::https://github.com/hashicorp/foo.git?foo=bar", + "https://github.com/hashicorp/foo.git?foo=bar", }, } pwd := "/pwd" - f := new(GitHubDetector) + f := new(GitGetter) for i, tc := range cases { output, ok, err := f.Detect(tc.Input, pwd) if err != nil { diff --git a/detect_s3.go b/detect_s3.go deleted file mode 100644 index 8e0f4a03b..000000000 --- a/detect_s3.go +++ /dev/null @@ -1,61 +0,0 @@ -package getter - -import ( - "fmt" - "net/url" - "strings" -) - -// S3Detector implements Detector to detect S3 URLs and turn -// them into URLs that the S3 getter can understand. -type S3Detector struct{} - -func (d *S3Detector) Detect(src, _ string) (string, bool, error) { - if len(src) == 0 { - return "", false, nil - } - - if strings.Contains(src, ".amazonaws.com/") { - return d.detectHTTP(src) - } - - return "", false, nil -} - -func (d *S3Detector) detectHTTP(src string) (string, bool, error) { - parts := strings.Split(src, "/") - if len(parts) < 2 { - return "", false, fmt.Errorf( - "URL is not a valid S3 URL") - } - - hostParts := strings.Split(parts[0], ".") - if len(hostParts) == 3 { - return d.detectPathStyle(hostParts[0], parts[1:]) - } else if len(hostParts) == 4 { - return d.detectVhostStyle(hostParts[1], hostParts[0], parts[1:]) - } else { - return "", false, fmt.Errorf( - "URL is not a valid S3 URL") - } -} - -func (d *S3Detector) detectPathStyle(region string, parts []string) (string, bool, error) { - urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s", region, strings.Join(parts, "/")) - url, err := url.Parse(urlStr) - if err != nil { - return "", false, fmt.Errorf("error parsing S3 URL: %s", err) - } - - return "s3::" + url.String(), true, nil -} - -func (d *S3Detector) detectVhostStyle(region, bucket string, parts []string) (string, bool, error) { - urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s/%s", region, bucket, strings.Join(parts, "/")) - url, err := url.Parse(urlStr) - if err != nil { - return "", false, fmt.Errorf("error parsing S3 URL: %s", err) - } - - return "s3::" + url.String(), true, nil -} diff --git a/detect_s3_test.go b/detect_s3_test.go index f410c785c..015efbdcb 100644 --- a/detect_s3_test.go +++ b/detect_s3_test.go @@ -12,62 +12,62 @@ func TestS3Detector(t *testing.T) { // Virtual hosted style { "bucket.s3.amazonaws.com/foo", - "s3::https://s3.amazonaws.com/bucket/foo", + "https://s3.amazonaws.com/bucket/foo", }, { "bucket.s3.amazonaws.com/foo/bar", - "s3::https://s3.amazonaws.com/bucket/foo/bar", + "https://s3.amazonaws.com/bucket/foo/bar", }, { "bucket.s3.amazonaws.com/foo/bar.baz", - "s3::https://s3.amazonaws.com/bucket/foo/bar.baz", + "https://s3.amazonaws.com/bucket/foo/bar.baz", }, { "bucket.s3-eu-west-1.amazonaws.com/foo", - "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo", + "https://s3-eu-west-1.amazonaws.com/bucket/foo", }, { "bucket.s3-eu-west-1.amazonaws.com/foo/bar", - "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", }, { "bucket.s3-eu-west-1.amazonaws.com/foo/bar.baz", - "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", }, // Path style { "s3.amazonaws.com/bucket/foo", - "s3::https://s3.amazonaws.com/bucket/foo", + "https://s3.amazonaws.com/bucket/foo", }, { "s3.amazonaws.com/bucket/foo/bar", - "s3::https://s3.amazonaws.com/bucket/foo/bar", + "https://s3.amazonaws.com/bucket/foo/bar", }, { "s3.amazonaws.com/bucket/foo/bar.baz", - "s3::https://s3.amazonaws.com/bucket/foo/bar.baz", + "https://s3.amazonaws.com/bucket/foo/bar.baz", }, { "s3-eu-west-1.amazonaws.com/bucket/foo", - "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo", + "https://s3-eu-west-1.amazonaws.com/bucket/foo", }, { "s3-eu-west-1.amazonaws.com/bucket/foo/bar", - "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", }, { "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", - "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", }, // Misc tests { "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", - "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", }, } pwd := "/pwd" - f := new(S3Detector) + f := new(S3Getter) for i, tc := range cases { output, ok, err := f.Detect(tc.Input, pwd) if err != nil { diff --git a/detect_smb.go b/detect_smb.go deleted file mode 100644 index 348a5b3df..000000000 --- a/detect_smb.go +++ /dev/null @@ -1,29 +0,0 @@ -package getter - -import ( - "fmt" - "path/filepath" - "runtime" - "strings" -) - -// SmbDetector implements Detector to detect smb paths with // for Windows OS. -type SmbDetector struct{} - -func (d *SmbDetector) Detect(src, pwd string) (string, bool, error) { - if len(src) == 0 { - return "", false, nil - } - - if windowsSmbPath(src) { - // This is a valid smb path for Windows and will be checked in the SmbGetter - // by the file system using the FileGetter, if available. - src = filepath.ToSlash(src) - return fmt.Sprintf("smb:%s", src), true, nil - } - return "", false, nil -} - -func windowsSmbPath(path string) bool { - return runtime.GOOS == "windows" && (strings.HasPrefix(path, "\\\\") || strings.HasPrefix(path, "//")) -} diff --git a/detect_test.go b/detect_test.go index 9bef662a7..4cbe37f4d 100644 --- a/detect_test.go +++ b/detect_test.go @@ -10,50 +10,58 @@ func TestDetect(t *testing.T) { Input string Pwd string Output string + g Getter Err bool }{ - {"./foo", "/foo", "file:///foo/foo", false}, - {"git::./foo", "/foo", "git::file:///foo/foo", false}, + {"./foo", "/foo", "/foo/foo", new(FileGetter), false}, + //{"git::./foo", "/foo", "/foo/foo", new(GitGetter),false}, // TODO @sylviamoss understand this test. Is this a real situation? { "git::github.com/hashicorp/foo", "", - "git::https://github.com/hashicorp/foo.git", + "https://github.com/hashicorp/foo.git", + new(GitGetter), false, }, { "./foo//bar", "/foo", - "file:///foo/foo//bar", + "/foo/foo//bar", + new(FileGetter), false, }, { "git::github.com/hashicorp/foo//bar", "", - "git::https://github.com/hashicorp/foo.git//bar", + "https://github.com/hashicorp/foo.git//bar", + new(GitGetter), false, }, { "git::https://github.com/hashicorp/consul.git", "", - "git::https://github.com/hashicorp/consul.git", + "https://github.com/hashicorp/consul.git", + new(GitGetter), false, }, { - "git::https://person@someothergit.com/foo/bar", + "git::https://person@someothergit.com/foo/bar", // this "", - "git::https://person@someothergit.com/foo/bar", + "https://person@someothergit.com/foo/bar", + new(GitGetter), false, }, { - "git::https://person@someothergit.com/foo/bar", + "git::https://person@someothergit.com/foo/bar", // this "/bar", - "git::https://person@someothergit.com/foo/bar", + "https://person@someothergit.com/foo/bar", + new(GitGetter), false, }, { "./foo/archive//*", "/bar", - "file:///bar/foo/archive//*", + "/bar/foo/archive//*", + new(FileGetter), false, }, @@ -61,29 +69,35 @@ func TestDetect(t *testing.T) { { "git::ssh://git@my.custom.git/dir1/dir2", "", - "git::ssh://git@my.custom.git/dir1/dir2", + "ssh://git@my.custom.git/dir1/dir2", + new(GitGetter), false, }, { "git::git@my.custom.git:dir1/dir2", "/foo", - "git::ssh://git@my.custom.git/dir1/dir2", + "ssh://git@my.custom.git/dir1/dir2", + new(GitGetter), false, }, { "git::git@my.custom.git:dir1/dir2", "", - "git::ssh://git@my.custom.git/dir1/dir2", + "ssh://git@my.custom.git/dir1/dir2", + new(GitGetter), false, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d %s", i, tc.Input), func(t *testing.T) { - output, err := Detect(tc.Input, tc.Pwd, Detectors) + output, ok, err := Detect(tc.Input, tc.Pwd, tc.g) if err != nil != tc.Err { t.Fatalf("%d: bad err: %s", i, err) } + if !ok { + t.Fatalf("%s url expected to valid", tc.Input) + } if output != tc.Output { t.Fatalf("%d: bad output: %s\nexpected: %s", i, output, tc.Output) } diff --git a/get.go b/get.go index e75067c62..78723cc56 100644 --- a/get.go +++ b/get.go @@ -43,15 +43,16 @@ type Getter interface { // allow clients to let the getters decide which mode to use. Mode(context.Context, *url.URL) (Mode, error) - // SetClient allows a getter to know it's client - // in order to access client's Get functions or - // progress tracking. - SetClient(*Client) + // Detect will detect whether the string matches a known pattern to + // turn it into a proper URL. + Detect(string, string) (string, bool, error) + + ValidScheme(string) bool } // Getters is the mapping of scheme to the Getter implementation that will // be used to get a dependency. -var Getters map[string]Getter +var Getters []Getter // forcedRegexp is the regular expression that finds forced getters. This // syntax is schema::url, example: git::https://foo.com @@ -62,7 +63,6 @@ var httpClient = cleanhttp.DefaultClient() var DefaultClient = &Client{ Getters: Getters, - Detectors: Detectors, Decompressors: Decompressors, } @@ -71,15 +71,15 @@ func init() { Netrc: true, } - Getters = map[string]Getter{ - "file": new(FileGetter), - "smb": new(SmbGetter), - "git": new(GitGetter), - "gcs": new(GCSGetter), - "hg": new(HgGetter), - "s3": new(S3Getter), - "http": httpGetter, - "https": httpGetter, + Getters = []Getter{ + new(GitGetter), + new(HgGetter), + new(S3Getter), + new(GCSGetter), + new(FileGetter), + new(SmbGetter), + httpGetter, + httpGetter, } } @@ -147,7 +147,7 @@ func getRunCommand(cmd *exec.Cmd) error { return fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) } -// getForcedGetter takes a source and returns the tuple of the forced +// removeForcedGetter takes a source and returns the tuple of the forced // getter and the raw URL (without the force syntax). func getForcedGetter(src string) (string, string) { var forced string diff --git a/get_base.go b/get_base.go deleted file mode 100644 index 4a9ca733f..000000000 --- a/get_base.go +++ /dev/null @@ -1,9 +0,0 @@ -package getter - -// getter is our base getter; it regroups -// fields all getters have in common. -type getter struct { - client *Client -} - -func (g *getter) SetClient(c *Client) { g.client = c } diff --git a/get_file.go b/get_file.go index f11d13ecf..18b647a8a 100644 --- a/get_file.go +++ b/get_file.go @@ -6,12 +6,12 @@ import ( "net/url" "os" "path/filepath" + "runtime" ) // FileGetter is a Getter implementation that will download a module from // a file scheme. type FileGetter struct { - getter } func (g *FileGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -149,3 +149,70 @@ func (g *FileGetter) GetFile(ctx context.Context, req *Request) error { _, err = Copy(ctx, dstF, srcF) return err } + +func (g *FileGetter) Detect(src, pwd string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + u, err := url.Parse(src) + if err == nil && u.Scheme == "file" { + // Valid URL + return src, true, nil + } + + + if !filepath.IsAbs(src) { + if pwd == "" { + return "", true, fmt.Errorf( + "relative paths require a module with a pwd") + } + + // Stat the pwd to determine if its a symbolic link. If it is, + // then the pwd becomes the original directory. Otherwise, + // `filepath.Join` below does some weird stuff. + // + // We just ignore if the pwd doesn't exist. That error will be + // caught later when we try to use the URL. + if fi, err := os.Lstat(pwd); !os.IsNotExist(err) { + if err != nil { + return "", true, err + } + if fi.Mode()&os.ModeSymlink != 0 { + pwd, err = filepath.EvalSymlinks(pwd) + if err != nil { + return "", true, err + } + + // The symlink itself might be a relative path, so we have to + // resolve this to have a correctly rooted URL. + pwd, err = filepath.Abs(pwd) + if err != nil { + return "", true, err + } + } + } + + src = filepath.Join(pwd, src) + } + + if windowsSmbPath(src) { + // This is a valid smb path for Windows and will be checked in the SmbGetter + // by the file system using the FileGetter, if available. + return src, false, nil + } + + return fmtFileURL(src), true, nil +} + +func (g *FileGetter) ValidScheme(scheme string) bool { + return scheme == "file" +} + +func fmtFileURL(path string) string { + if runtime.GOOS == "windows" { + // Make sure we're using "/" on Windows. URLs are "/"-based. + path = filepath.ToSlash(path) + } + return path +} diff --git a/get_gcs.go b/get_gcs.go index a1c6409c1..daf9d283f 100644 --- a/get_gcs.go +++ b/get_gcs.go @@ -15,7 +15,6 @@ import ( // GCSGetter is a Getter implementation that will download a module from // a GCS bucket. type GCSGetter struct { - getter } func (g *GCSGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -165,3 +164,45 @@ func (g *GCSGetter) parseURL(u *url.URL) (bucket, path string, err error) { } return } + +func (g *GCSGetter) Detect(src, _ string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + u, err := url.Parse(src) + if err == nil && u.Scheme == "gcs" { + // Valid URL + return src, true, nil + } + + if strings.Contains(src, "googleapis.com/") { + return g.detectHTTP(src) + } + + return "", false, nil +} + +func (g *GCSGetter) detectHTTP(src string) (string, bool, error) { + + parts := strings.Split(src, "/") + if len(parts) < 5 { + return "", false, fmt.Errorf( + "URL is not a valid GCS URL") + } + version := parts[2] + bucket := parts[3] + object := strings.Join(parts[4:], "/") + + url, err := url.Parse(fmt.Sprintf("https://www.googleapis.com/storage/%s/%s/%s", + version, bucket, object)) + if err != nil { + return "", false, fmt.Errorf("error parsing GCS URL: %s", err) + } + + return url.String(), true, nil +} + +func (g *GCSGetter) ValidScheme(scheme string) bool { + return scheme == "gcs" +} diff --git a/get_git.go b/get_git.go index 4a0b044f7..f82ee179d 100644 --- a/get_git.go +++ b/get_git.go @@ -4,8 +4,10 @@ import ( "bytes" "context" "encoding/base64" + "encoding/json" "fmt" "io/ioutil" + "net/http" "net/url" "os" "os/exec" @@ -23,7 +25,6 @@ import ( // GitGetter is a Getter implementation that will download a module from // a git repository. type GitGetter struct { - getter } var defaultBranchRegexp = regexp.MustCompile(`\s->\sorigin/(.*)`) @@ -314,3 +315,112 @@ func checkGitVersion(min string) error { return nil } + +func (g *GitGetter) Detect(src, _ string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + u, err := url.Parse(src) + if err == nil && (u.Scheme == "git" || u.Scheme == "ssh") { + // Valid URL + return src, true, nil + } + + u, err = detectSSH(src) + if err != nil { + return "", true, err + } + if u != nil { + // We require the username to be "git" to assume that this is a Git URL + if u.User.Username() == "git" { + return u.String(), true, nil + } + } + + result, ok, err := detectGitHub(src) + if err != nil { + return "", ok, err + } + if ok { + return result, ok, nil + } + + result, u, err = detectBitBucket(src) + if err != nil { + return "", true, err + } + if result == "git" { + if !strings.HasSuffix(u.Path, ".git") { + u.Path += ".git" + } + return u.String(), true, nil + } + + return "", false, nil +} + +func detectBitBucket(src string) (string, *url.URL, error) { + if !strings.HasPrefix(src, "bitbucket.org/") { + return "", nil, nil + } + + u, err := url.Parse("https://" + src) + if err != nil { + return "", nil, fmt.Errorf("error parsing BitBucket URL: %s", err) + } + + // We need to get info on this BitBucket repository to determine whether + // it is Git or Hg. + var info struct { + SCM string `json:"scm"` + } + infoUrl := "https://api.bitbucket.org/2.0/repositories" + u.Path + resp, err := http.Get(infoUrl) + if err != nil { + return "", nil, fmt.Errorf("error looking up BitBucket URL: %s", err) + } + if resp.StatusCode == 403 { + // A private repo + return "", nil, fmt.Errorf( + "shorthand BitBucket URL can't be used for private repos, " + + "please use a full URL") + } + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&info); err != nil { + return "", nil, fmt.Errorf("error looking up BitBucket URL: %s", err) + } + + return info.SCM, u, nil +} + +func detectGitHub(src string) (string, bool, error) { + if !strings.HasPrefix(src, "github.com/") { + return "", false, nil + } + parts := strings.Split(src, "/") + if len(parts) < 3 { + return "", false, fmt.Errorf( + "GitHub URLs should be github.com/username/repo") + } + + urlStr := fmt.Sprintf("https://%s", strings.Join(parts[:3], "/")) + url, err := url.Parse(urlStr) + if err != nil { + return "", true, fmt.Errorf("error parsing GitHub URL: %s", err) + } + + if !strings.HasSuffix(url.Path, ".git") { + url.Path += ".git" + } + + if len(parts) > 3 { + url.Path += "//" + strings.Join(parts[3:], "/") + } + + return url.String(), true, nil +} + +func (g *GitGetter) ValidScheme(scheme string) bool { + return scheme == "git" || scheme == "ssh" +} \ No newline at end of file diff --git a/get_git_test.go b/get_git_test.go index eedba0ed4..94b80294c 100644 --- a/get_git_test.go +++ b/get_git_test.go @@ -393,7 +393,6 @@ func TestGitGetter_sshSCPStyle(t *testing.T) { } ctx := context.Background() - g := new(GitGetter) dst := tempDir(t) encodedKey := base64.StdEncoding.EncodeToString([]byte(testGitToken)) @@ -412,12 +411,7 @@ func TestGitGetter_sshSCPStyle(t *testing.T) { Mode: ModeDir, } client := &Client{ - Detectors: []Detector{ - new(GitDetector), - }, - Getters: map[string]Getter{ - "git": g, - }, + Getters:[]Getter{new(GitGetter)}, } if _, err := client.Get(ctx, req); err != nil { @@ -436,7 +430,6 @@ func TestGitGetter_sshExplicitPort(t *testing.T) { } ctx := context.Background() - g := new(GitGetter) dst := tempDir(t) encodedKey := base64.StdEncoding.EncodeToString([]byte(testGitToken)) @@ -455,13 +448,7 @@ func TestGitGetter_sshExplicitPort(t *testing.T) { Mode: ModeDir, } client := &Client{ - - Detectors: []Detector{ - new(GitDetector), - }, - Getters: map[string]Getter{ - "git": g, - }, + Getters: []Getter{new(GitGetter)}, } if _, err := client.Get(ctx, req); err != nil { @@ -480,7 +467,6 @@ func TestGitGetter_sshSCPStyleInvalidScheme(t *testing.T) { } ctx := context.Background() - g := new(GitGetter) dst := tempDir(t) encodedKey := base64.StdEncoding.EncodeToString([]byte(testGitToken)) @@ -500,12 +486,7 @@ func TestGitGetter_sshSCPStyleInvalidScheme(t *testing.T) { } client := &Client{ - Detectors: []Detector{ - new(GitDetector), - }, - Getters: map[string]Getter{ - "git": g, - }, + Getters: []Getter{new(GitGetter)}, } _, err := client.Get(ctx, req) @@ -514,7 +495,7 @@ func TestGitGetter_sshSCPStyleInvalidScheme(t *testing.T) { } got := err.Error() - want1, want2 := `invalid source string`, `invalid port number "hashicorp"` + want1, want2 := `invalid source string`, `invalid port ":hashicorp"` if !(strings.Contains(got, want1) || strings.Contains(got, want2)) { t.Fatalf("wrong error\ngot: %s\nwant: %q or %q", got, want1, want2) } diff --git a/get_hg.go b/get_hg.go index 4532113e4..65aeef1c8 100644 --- a/get_hg.go +++ b/get_hg.go @@ -3,20 +3,18 @@ package getter import ( "context" "fmt" + urlhelper "github.com/hashicorp/go-getter/helper/url" + safetemp "github.com/hashicorp/go-safetemp" "net/url" "os" "os/exec" "path/filepath" "runtime" - - urlhelper "github.com/hashicorp/go-getter/helper/url" - safetemp "github.com/hashicorp/go-safetemp" ) // HgGetter is a Getter implementation that will download a module from // a Mercurial repository. type HgGetter struct { - getter } func (g *HgGetter) Mode(ctx context.Context, _ *url.URL) (Mode, error) { @@ -126,6 +124,33 @@ func (g *HgGetter) update(ctx context.Context, dst string, u *url.URL, rev strin return getRunCommand(cmd) } +func (g *HgGetter) Detect(src, _ string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + u, err := url.Parse(src) + if err == nil && u.Scheme == "hg" { + // Valid URL + return src, true, nil + } + + result, u, err := detectBitBucket(src) + if err != nil { + return "", true, err + } + if result == "hg" { + return u.String(), true, nil + } + + return "", false, nil +} + + +func (g *HgGetter) ValidScheme(scheme string) bool { + return scheme == "hg" +} + func fixWindowsDrivePath(u *url.URL) bool { // hg assumes a file:/// prefix for Windows drive letter file paths. // (e.g. file:///c:/foo/bar) diff --git a/get_http.go b/get_http.go index 27671b32d..d4f1aaf74 100644 --- a/get_http.go +++ b/get_http.go @@ -35,8 +35,6 @@ import ( // formed URL. The shorthand syntax of "github.com/foo/bar" or relative // paths are not allowed. type HttpGetter struct { - getter - // Netrc, if true, will lookup and use auth information found // in the user's netrc file if available. Netrc bool @@ -320,3 +318,16 @@ func charsetReader(charset string, input io.Reader) (io.Reader, error) { return nil, fmt.Errorf("can't decode XML document using charset %q", charset) } } + +func (g *HttpGetter) Detect(src, _ string) (string, bool, error) { + u, err := url.Parse(src) + if err == nil && u.Scheme != "" && (u.Scheme == "http" || u.Scheme == "https") { + // Valid URL + return src, true, nil + } + return "", false, nil +} + +func (g *HttpGetter) ValidScheme(scheme string) bool { + return scheme == "http" || scheme == "https" +} diff --git a/get_mock.go b/get_mock.go index 26ea38143..32f0f915d 100644 --- a/get_mock.go +++ b/get_mock.go @@ -7,8 +7,6 @@ import ( // MockGetter is an implementation of Getter that can be used for tests. type MockGetter struct { - getter - // Proxy, if set, will be called after recording the calls below. // If it isn't set, then the *Err values will be returned. Proxy Getter @@ -53,3 +51,11 @@ func (g *MockGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { } return ModeFile, nil } + +func (g *MockGetter) Detect(src, _ string) (string, bool, error) { + return "", true, nil +} + +func (g *MockGetter) ValidScheme(scheme string) bool { + return true +} diff --git a/get_s3.go b/get_s3.go index 77d8fb317..87cfd6410 100644 --- a/get_s3.go +++ b/get_s3.go @@ -19,7 +19,6 @@ import ( // S3Getter is a Getter implementation that will download a module from // a S3 bucket. type S3Getter struct { - getter } func (g *S3Getter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -271,3 +270,63 @@ func (g *S3Getter) parseUrl(u *url.URL) (region, bucket, path, version string, c return } + +func (g *S3Getter) Detect(src, _ string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + u, err := url.Parse(src) + if err == nil && u.Scheme == "s3" { + // Valid URL + return src, true, nil + } + + if strings.Contains(src, ".amazonaws.com/") { + return g.detectHTTP(src) + } + + return "", false, nil +} + +func (g *S3Getter) detectHTTP(src string) (string, bool, error) { + parts := strings.Split(src, "/") + if len(parts) < 2 { + return "", false, fmt.Errorf( + "URL is not a valid S3 URL") + } + + hostParts := strings.Split(parts[0], ".") + if len(hostParts) == 3 { + return g.detectPathStyle(hostParts[0], parts[1:]) + } else if len(hostParts) == 4 { + return g.detectVhostStyle(hostParts[1], hostParts[0], parts[1:]) + } else { + return "", false, fmt.Errorf( + "URL is not a valid S3 URL") + } +} + +func (g *S3Getter) detectPathStyle(region string, parts []string) (string, bool, error) { + urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s", region, strings.Join(parts, "/")) + url, err := url.Parse(urlStr) + if err != nil { + return "", false, fmt.Errorf("error parsing S3 URL: %s", err) + } + + return url.String(), true, nil +} + +func (g *S3Getter) detectVhostStyle(region, bucket string, parts []string) (string, bool, error) { + urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s/%s", region, bucket, strings.Join(parts, "/")) + url, err := url.Parse(urlStr) + if err != nil { + return "", false, fmt.Errorf("error parsing S3 URL: %s", err) + } + + return url.String(), true, nil +} + +func (g *S3Getter) ValidScheme(scheme string) bool { + return scheme == "s3" +} diff --git a/get_smb.go b/get_smb.go index 852b9feef..7519c1311 100644 --- a/get_smb.go +++ b/get_smb.go @@ -9,7 +9,6 @@ import ( "os/exec" "path/filepath" "regexp" - "runtime" "strings" "syscall" ) @@ -17,7 +16,6 @@ import ( // SmbGetter is a Getter implementation that will download a module from // a shared folder using smbclient cli for Unix and local file system for Windows. type SmbGetter struct { - getter } func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -25,16 +23,6 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return 0, new(smbPathError) } - // For windows, look for the shared folder withing the file system - if g.client != nil { - fileGetter, ok := g.client.Getters["file"] - if runtime.GOOS == "windows" && ok { - prefix := string(os.PathSeparator) + string(os.PathSeparator) - u.Path = prefix + filepath.Join(u.Host, u.Path) - return fileGetter.Mode(ctx, u) - } - } - // Use smbclient cli to verify mode mode, err := g.smbClientMode(u) if err == nil { @@ -84,16 +72,6 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { } } - // For windows, look for the shared folder withing the file system - if g.client != nil { - fileGetter, ok := g.client.Getters["file"] - if runtime.GOOS == "windows" && ok { - prefix := string(os.PathSeparator) + string(os.PathSeparator) - req.u.Path = prefix + filepath.Join(req.u.Host, req.u.Path) - return fileGetter.Get(ctx, req) - } - } - // Download the directory content using smbclient cli err := g.smbclientGet(req) if err == nil { @@ -159,16 +137,6 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { } } - // For windows, look for the shared folder withing the file system - if g.client != nil { - fileGetter, ok := g.client.Getters["file"] - if runtime.GOOS == "windows" && ok { - prefix := string(os.PathSeparator) + string(os.PathSeparator) - req.u.Path = prefix + filepath.Join(req.u.Host, req.u.Path) - return fileGetter.GetFile(ctx, req) - } - } - // If not mounted, try downloading the file using smbclient cli err := g.smbclientGetFile(req) if err == nil { @@ -314,6 +282,25 @@ func (g *SmbGetter) runSmbClientCommand(dst string, args []string) (string, erro return buf.String(), fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) } +func (g *SmbGetter) Detect(src, pwd string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + u, err := url.Parse(src) + if err == nil && u.Scheme == "smb" { + // Valid URL + return src, true, nil + } + + return "", false, nil +} + + +func (g *SmbGetter) ValidScheme(scheme string) bool { + return scheme == "smb" +} + type smbPathError struct { Path string } diff --git a/get_smb_test.go b/get_smb_test.go index 84b1a47d9..516342e81 100644 --- a/get_smb_test.go +++ b/get_smb_test.go @@ -98,7 +98,6 @@ func TestSmb_GetterGet(t *testing.T) { } g := new(SmbGetter) - g.SetClient(DefaultClient) err = g.Get(context.Background(), req) fail := err != nil @@ -216,7 +215,6 @@ func TestSmb_GetterGetFile(t *testing.T) { } g := new(SmbGetter) - g.SetClient(DefaultClient) err = g.GetFile(context.Background(), req) fail := err != nil @@ -303,7 +301,6 @@ func TestSmb_GetterMode(t *testing.T) { } g := new(SmbGetter) - g.SetClient(DefaultClient) mode, err := g.Mode(context.Background(), url) fail := err != nil diff --git a/get_smb_win.go b/get_smb_win.go new file mode 100644 index 000000000..45a465ecf --- /dev/null +++ b/get_smb_win.go @@ -0,0 +1,91 @@ +package getter + +import ( + "context" + "net/url" + "os" + "path/filepath" + "runtime" + "strings" +) + +// SmbGetter is a Getter implementation that will download a module from +// a shared folder using smbclient cli for Unix and local file system for Windows. +type SmbGetterWindows struct { +} + +func (g *SmbGetterWindows) Mode(ctx context.Context, u *url.URL) (Mode, error) { + if u.Host == "" || u.Path == "" { + return 0, new(smbPathError) + } + + if runtime.GOOS == "windows" { + prefix := string(os.PathSeparator) + string(os.PathSeparator) + u.Path = prefix + filepath.Join(u.Host, u.Path) + return new(FileGetter).Mode(ctx, u) + } + + return 0, nil +} + +func (g *SmbGetterWindows) Get(ctx context.Context, req *Request) error { + if req.u.Host == "" || req.u.Path == "" { + return new(smbPathError) + } + + if runtime.GOOS == "windows" { + prefix := string(os.PathSeparator) + string(os.PathSeparator) + req.u.Path = prefix + filepath.Join(req.u.Host, req.u.Path) + return new(FileGetter).Get(ctx, req) + } + + return nil +} + +func (g *SmbGetterWindows) GetFile(ctx context.Context, req *Request) error { + if req.u.Host == "" || req.u.Path == "" { + return new(smbPathError) + } + + if runtime.GOOS == "windows" { + prefix := string(os.PathSeparator) + string(os.PathSeparator) + req.u.Path = prefix + filepath.Join(req.u.Host, req.u.Path) + return new(FileGetter).GetFile(ctx, req) + } + + return nil +} + +func (g *SmbGetterWindows) Detect(src, pwd string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + // Don't even try SmbGetterWindows if is not Windows + if runtime.GOOS != "windows" { + return "", false, nil + } + + u, err := url.Parse(src) + if err == nil && u.Scheme == "smb" { + // Valid URL + return src, true, nil + } + + if windowsSmbPath(src) { + // This is a valid smb path for Windows and will be checked in the SmbGetter + // by the file system using the FileGetter, if available. + return filepath.ToSlash(src), true, nil + } + + return "", false, nil +} + +func windowsSmbPath(path string) bool { + return runtime.GOOS == "windows" && (strings.HasPrefix(path, "\\\\") || strings.HasPrefix(path, "//")) +} + +func (g *SmbGetterWindows) ValidScheme(scheme string) bool { + return scheme == "smb" +} + diff --git a/get_test.go b/get_test.go index 7b35dd02d..1916a02db 100644 --- a/get_test.go +++ b/get_test.go @@ -15,7 +15,7 @@ func TestGet_badSchema(t *testing.T) { dst := tempDir(t) u := testModule("basic") - u = strings.Replace(u, "file", "nope", -1) + u = "nope::" + u op, err := Get(ctx, dst, u) if err == nil { @@ -588,9 +588,7 @@ func TestGetFile_checksumURL(t *testing.T) { Mode: ModeFile, } client := &Client{ - Getters: map[string]Getter{ - "file": getter, - }, + Getters: []Getter{ getter}, } op, err := client.Get(ctx, req) @@ -643,9 +641,7 @@ func TestGetFile_checksumSkip(t *testing.T) { Mode: ModeFile, } client := &Client{ - Getters: map[string]Getter{ - "file": getter, - }, + Getters: []Getter{getter}, } // get the file @@ -693,9 +689,7 @@ func TestGetFile_inplace(t *testing.T) { Inplace: true, } client := &Client{ - Getters: map[string]Getter{ - "file": getter, - }, + Getters: []Getter{ getter}, } // get the file @@ -743,9 +737,7 @@ func TestGetFile_inplace_badChecksum(t *testing.T) { Inplace: true, } client := &Client{ - Getters: map[string]Getter{ - "file": getter, - }, + Getters: []Getter{getter}, } // get the file From ac4a6cb2fb5a9adc4e64a8b56d46a73e1522b998 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 30 Apr 2020 18:13:35 +0200 Subject: [PATCH 076/109] fix format --- detect.go | 6 +++--- detect_file_test.go | 20 ++++++++++---------- detect_test.go | 6 +++--- get_file.go | 1 - get_git.go | 2 +- get_git_test.go | 2 +- get_hg.go | 1 - get_smb.go | 1 - get_smb_win.go | 1 - get_test.go | 4 ++-- 10 files changed, 20 insertions(+), 24 deletions(-) diff --git a/detect.go b/detect.go index 2aa40e91c..389453ced 100644 --- a/detect.go +++ b/detect.go @@ -19,9 +19,9 @@ import ( func Detect(src string, pwd string, g Getter) (string, bool, error) { getForce, getSrc := getForcedGetter(src) - if getForce != "" && !g.ValidScheme(getForce){ - // Another getter is being forced - return "", false, nil + if getForce != "" && !g.ValidScheme(getForce) { + // Another getter is being forced + return "", false, nil } isForcedGetter := getForce != "" && g.ValidScheme(getForce) diff --git a/detect_file_test.go b/detect_file_test.go index 0c7fc836e..dbe794d4c 100644 --- a/detect_file_test.go +++ b/detect_file_test.go @@ -10,27 +10,27 @@ import ( type fileTest struct { in, pwd, out string - symlink, err bool + symlink, err bool } var fileTests = []fileTest{ - {"./foo", "/pwd", "/pwd/foo", false,false}, - {"./foo?foo=bar", "/pwd", "/pwd/foo?foo=bar", false,false}, - {"foo", "/pwd", "/pwd/foo", false,false}, + {"./foo", "/pwd", "/pwd/foo", false, false}, + {"./foo?foo=bar", "/pwd", "/pwd/foo?foo=bar", false, false}, + {"foo", "/pwd", "/pwd/foo", false, false}, } var unixFileTests = []fileTest{ {"./foo", "testdata/detect-file-symlink-pwd/syml/pwd", - "testdata/detect-file-symlink-pwd/real/foo", true,false}, + "testdata/detect-file-symlink-pwd/real/foo", true, false}, - {"/foo", "/pwd", "/foo", false,false}, - {"/foo?bar=baz", "/pwd", "/foo?bar=baz", false,false}, + {"/foo", "/pwd", "/foo", false, false}, + {"/foo?bar=baz", "/pwd", "/foo?bar=baz", false, false}, } var winFileTests = []fileTest{ - {"/foo", "/pwd", "/pwd/foo", false,false}, - {`C:\`, `/pwd`, `C:/`, false,false}, - {`C:\?bar=baz`, `/pwd`, `C:/?bar=baz`, false,false}, + {"/foo", "/pwd", "/pwd/foo", false, false}, + {`C:\`, `/pwd`, `C:/`, false, false}, + {`C:\?bar=baz`, `/pwd`, `C:/?bar=baz`, false, false}, } func TestFileDetector(t *testing.T) { diff --git a/detect_test.go b/detect_test.go index 4cbe37f4d..42e02b199 100644 --- a/detect_test.go +++ b/detect_test.go @@ -10,11 +10,11 @@ func TestDetect(t *testing.T) { Input string Pwd string Output string - g Getter + g Getter Err bool }{ - {"./foo", "/foo", "/foo/foo", new(FileGetter), false}, - //{"git::./foo", "/foo", "/foo/foo", new(GitGetter),false}, // TODO @sylviamoss understand this test. Is this a real situation? + {"./foo", "/foo", "/foo/foo", new(FileGetter), false}, + //{"git::./foo", "/foo", "/foo/foo", new(GitGetter),false}, // TODO @sylviamoss understand this test. Is this a real situation? { "git::github.com/hashicorp/foo", "", diff --git a/get_file.go b/get_file.go index 18b647a8a..aeb82bbd8 100644 --- a/get_file.go +++ b/get_file.go @@ -161,7 +161,6 @@ func (g *FileGetter) Detect(src, pwd string) (string, bool, error) { return src, true, nil } - if !filepath.IsAbs(src) { if pwd == "" { return "", true, fmt.Errorf( diff --git a/get_git.go b/get_git.go index f82ee179d..f167c96f9 100644 --- a/get_git.go +++ b/get_git.go @@ -423,4 +423,4 @@ func detectGitHub(src string) (string, bool, error) { func (g *GitGetter) ValidScheme(scheme string) bool { return scheme == "git" || scheme == "ssh" -} \ No newline at end of file +} diff --git a/get_git_test.go b/get_git_test.go index 94b80294c..cbfa0036d 100644 --- a/get_git_test.go +++ b/get_git_test.go @@ -411,7 +411,7 @@ func TestGitGetter_sshSCPStyle(t *testing.T) { Mode: ModeDir, } client := &Client{ - Getters:[]Getter{new(GitGetter)}, + Getters: []Getter{new(GitGetter)}, } if _, err := client.Get(ctx, req); err != nil { diff --git a/get_hg.go b/get_hg.go index 65aeef1c8..e81692a70 100644 --- a/get_hg.go +++ b/get_hg.go @@ -146,7 +146,6 @@ func (g *HgGetter) Detect(src, _ string) (string, bool, error) { return "", false, nil } - func (g *HgGetter) ValidScheme(scheme string) bool { return scheme == "hg" } diff --git a/get_smb.go b/get_smb.go index 7519c1311..054b17cff 100644 --- a/get_smb.go +++ b/get_smb.go @@ -296,7 +296,6 @@ func (g *SmbGetter) Detect(src, pwd string) (string, bool, error) { return "", false, nil } - func (g *SmbGetter) ValidScheme(scheme string) bool { return scheme == "smb" } diff --git a/get_smb_win.go b/get_smb_win.go index 45a465ecf..d024b2b65 100644 --- a/get_smb_win.go +++ b/get_smb_win.go @@ -88,4 +88,3 @@ func windowsSmbPath(path string) bool { func (g *SmbGetterWindows) ValidScheme(scheme string) bool { return scheme == "smb" } - diff --git a/get_test.go b/get_test.go index 1916a02db..f8720a9c0 100644 --- a/get_test.go +++ b/get_test.go @@ -588,7 +588,7 @@ func TestGetFile_checksumURL(t *testing.T) { Mode: ModeFile, } client := &Client{ - Getters: []Getter{ getter}, + Getters: []Getter{getter}, } op, err := client.Get(ctx, req) @@ -689,7 +689,7 @@ func TestGetFile_inplace(t *testing.T) { Inplace: true, } client := &Client{ - Getters: []Getter{ getter}, + Getters: []Getter{getter}, } // get the file From 66ada4f3ec43bc9f6c305bdb954fe22e81e10513 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 4 May 2020 15:06:40 +0200 Subject: [PATCH 077/109] create detector chain of responsibility --- client.go | 179 +++++++++++++++-------------- detect.go | 75 ------------ detect_bitbucket_test.go | 2 +- detect_file_test.go | 4 +- detect_gcs_test.go | 2 +- detect_git_test.go | 8 +- detect_github_test.go | 2 +- detect_s3_test.go | 2 +- detect_test.go => detector_test.go | 33 +++--- get.go | 9 +- get_detector.go | 157 +++++++++++++++++++++++++ get_file.go | 15 ++- get_gcs.go | 15 ++- get_git.go | 15 ++- get_git_test.go | 6 - get_hg.go | 15 ++- get_http.go | 16 ++- get_mock.go | 26 ++++- get_s3.go | 15 ++- get_smb.go | 15 ++- 20 files changed, 404 insertions(+), 207 deletions(-) delete mode 100644 detect.go rename detect_test.go => detector_test.go (77%) create mode 100644 get_detector.go diff --git a/client.go b/client.go index 6a5e6564b..de898f62f 100644 --- a/client.go +++ b/client.go @@ -3,6 +3,7 @@ package getter import ( "context" "fmt" + "github.com/hashicorp/go-multierror" "io/ioutil" "os" "path/filepath" @@ -45,109 +46,107 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { req.Mode = ModeAny } + executor := NewGetterDetector(c.Getters) + // Loop over Getters in priority order. // Some urls can be supported by more than one Getter and we want to make sure // the best getter option will try to download first - for _, g := range c.Getters { - src, ok, err := Detect(req.Src, req.Pwd, g) + src, err := executor.Detect(req.Src, req.Pwd) + if err != nil { + return nil, err + } + req.Src = src + + // If there is a subdir component, then we download the root separately + // and then copy over the proper subdir. + var realDst, subDir string + req.Src, subDir = SourceDirSubdir(req.Src) + if subDir != "" { + td, tdcloser, err := safetemp.Dir("", "getter") if err != nil { return nil, err } - if !ok { - continue - } - req.Src = src + defer tdcloser.Close() - // If there is a subdir component, then we download the root separately - // and then copy over the proper subdir. - var realDst, subDir string - req.Src, subDir = SourceDirSubdir(req.Src) - if subDir != "" { - // TODO @sylviamoss different dirs for different getters - td, tdcloser, err := safetemp.Dir("", "getter") - if err != nil { - return nil, err - } - defer tdcloser.Close() + realDst = req.Dst + req.Dst = td + } - realDst = req.Dst - req.Dst = td - } + req.u, err = urlhelper.Parse(req.Src) + if err != nil { + return nil, err + } - req.u, err = urlhelper.Parse(req.Src) - if err != nil { - //return nil, err - // TODO @sylviamoss save error - continue - } + // We have magic query parameters that we use to signal different features + q := req.u.Query() - // We have magic query parameters that we use to signal different features - q := req.u.Query() - - // Determine if we have an archive type - archiveV := q.Get("archive") - if archiveV != "" { - // Delete the paramter since it is a magic parameter we don't - // want to pass on to the Getter - q.Del("archive") - req.u.RawQuery = q.Encode() - - // If we can parse the value as a bool and it is false, then - // set the archive to "-" which should never map to a decompressor - if b, err := strconv.ParseBool(archiveV); err == nil && !b { - archiveV = "-" - } - } else { - // We don't appear to... but is it part of the filename? - matchingLen := 0 - for k := range c.Decompressors { - if strings.HasSuffix(req.u.Path, "."+k) && len(k) > matchingLen { - archiveV = k - matchingLen = len(k) - } - } - } + // Determine if we have an archive type + archiveV := q.Get("archive") + if archiveV != "" { + // Delete the paramter since it is a magic parameter we don't + // want to pass on to the Getter + q.Del("archive") + req.u.RawQuery = q.Encode() - // If we have a decompressor, then we need to change the destination - // to download to a temporary path. We unarchive this into the final, - // real path. - var decompressDst string - var decompressDir bool - decompressor := c.Decompressors[archiveV] - if decompressor != nil { - // Create a temporary directory to store our archive. We delete - // this at the end of everything. - td, err := ioutil.TempDir("", "getter") - if err != nil { - return nil, fmt.Errorf( - "Error creating temporary directory for archive: %s", err) + // If we can parse the value as a bool and it is false, then + // set the archive to "-" which should never map to a decompressor + if b, err := strconv.ParseBool(archiveV); err == nil && !b { + archiveV = "-" + } + } else { + // We don't appear to... but is it part of the filename? + matchingLen := 0 + for k := range c.Decompressors { + if strings.HasSuffix(req.u.Path, "."+k) && len(k) > matchingLen { + archiveV = k + matchingLen = len(k) } - defer os.RemoveAll(td) - - // Swap the download directory to be our temporary path and - // store the old values. - decompressDst = req.Dst - decompressDir = req.Mode != ModeFile - req.Dst = filepath.Join(td, "archive") - req.Mode = ModeFile } + } - // Determine checksum if we have one - checksum, err := c.extractChecksum(ctx, req.u) + // If we have a decompressor, then we need to change the destination + // to download to a temporary path. We unarchive this into the final, + // real path. + var decompressDst string + var decompressDir bool + decompressor := c.Decompressors[archiveV] + if decompressor != nil { + // Create a temporary directory to store our archive. We delete + // this at the end of everything. + td, err := ioutil.TempDir("", "getter") if err != nil { - return nil, fmt.Errorf("invalid checksum: %s", err) + return nil, fmt.Errorf( + "Error creating temporary directory for archive: %s", err) } + defer os.RemoveAll(td) + + // Swap the download directory to be our temporary path and + // store the old values. + decompressDst = req.Dst + decompressDir = req.Mode != ModeFile + req.Dst = filepath.Join(td, "archive") + req.Mode = ModeFile + } - // Delete the query parameter if we have it. - q.Del("checksum") - req.u.RawQuery = q.Encode() + // Determine checksum if we have one + checksum, err := c.extractChecksum(ctx, req.u) + if err != nil { + return nil, fmt.Errorf("invalid checksum: %s", err) + } + + // Delete the query parameter if we have it. + q.Del("checksum") + req.u.RawQuery = q.Encode() + var modeErr *multierror.Error + var getFileErr *multierror.Error + var getErr *multierror.Error + for _, g := range executor.getters { if req.Mode == ModeAny { // Ask the getter which client mode to use req.Mode, err = g.Mode(ctx, req.u) if err != nil { - //return nil, err - // TODO @sylviamoss save error + modeErr = multierror.Append(modeErr, err) continue } @@ -182,8 +181,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { if getFile { err := g.GetFile(ctx, req) if err != nil { - //return nil, err - // TODO @sylviamoss save error + getFileErr = multierror.Append(getFileErr, err) continue } @@ -235,9 +233,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // if we're specifying a subdir. err := g.Get(ctx, req) if err != nil { - //err = fmt.Errorf("error downloading '%s': %s", req.Src, err) - //return nil, err - // TODO @sylviamoss save error + getErr = multierror.Append(getErr, err) continue } } @@ -262,7 +258,16 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { return &GetResult{req.Dst}, nil } - return nil, fmt.Errorf("failed to download %s", req.Src) + if getErr != nil { + err = fmt.Errorf("error downloading '%s': %s", req.Src, getErr) + } + if getFileErr != nil { + err = fmt.Errorf("error downloading '%s': %s", req.Src, getFileErr) + } + if modeErr != nil { + err = fmt.Errorf("error downloading '%s': %s", req.Src, modeErr) + } + return nil, fmt.Errorf("error downloading '%s'", req.Src) } func (c *Client) checkArchive(req *Request) string { diff --git a/detect.go b/detect.go deleted file mode 100644 index 389453ced..000000000 --- a/detect.go +++ /dev/null @@ -1,75 +0,0 @@ -package getter - -import ( - "fmt" - "path/filepath" - - "github.com/hashicorp/go-getter/helper/url" -) - -// Detect turns a source string into another source string if it is -// detected to be of a known pattern. -// -// The third parameter should be the list of detectors to use in the -// order to try them. If you don't want to configure this, just use -// the global Detectors variable. -// -// This is safe to be called with an already valid source string: Detect -// will just return it. -func Detect(src string, pwd string, g Getter) (string, bool, error) { - getForce, getSrc := getForcedGetter(src) - - if getForce != "" && !g.ValidScheme(getForce) { - // Another getter is being forced - return "", false, nil - } - - isForcedGetter := getForce != "" && g.ValidScheme(getForce) - - // Separate out the subdir if there is one, we don't pass that to detect - getSrc, subDir := SourceDirSubdir(getSrc) - - if !isForcedGetter { - u, err := url.Parse(getSrc) - if err == nil && u.Scheme != "" { - return getSrc, g.ValidScheme(u.Scheme), nil - } - } - - result, ok, err := g.Detect(getSrc, pwd) - if err != nil { - return "", false, err - } - if !ok { - // If is this is the forced getter we return true even the detection wasn't valid - return getSrc, isForcedGetter, nil - } - - result, detectSubdir := SourceDirSubdir(result) - - // If we have a subdir from the detection, then prepend it to our - // requested subdir. - if detectSubdir != "" { - if subDir != "" { - subDir = filepath.Join(detectSubdir, subDir) - } else { - subDir = detectSubdir - } - } - - if subDir != "" { - u, err := url.Parse(result) - if err != nil { - return "", false, fmt.Errorf("Error parsing URL: %s", err) - } - u.Path += "//" + subDir - - // a subdir may contain wildcards, but in order to support them we - // have to ensure the path isn't escaped. - u.RawPath = u.Path - - result = u.String() - } - - return result, true, nil -} diff --git a/detect_bitbucket_test.go b/detect_bitbucket_test.go index e9c4c256f..b04302b29 100644 --- a/detect_bitbucket_test.go +++ b/detect_bitbucket_test.go @@ -45,7 +45,7 @@ func TestBitBucketDetector(t *testing.T) { for i := 0; i < 3; i++ { var output string var ok bool - output, ok, err = tc.g.Detect(tc.Input, pwd) + output, ok, err = tc.g.DetectGetter(tc.Input, pwd) if err != nil { if strings.Contains(err.Error(), "invalid character") { continue diff --git a/detect_file_test.go b/detect_file_test.go index dbe794d4c..5f100fee6 100644 --- a/detect_file_test.go +++ b/detect_file_test.go @@ -55,7 +55,7 @@ func TestFileDetector(t *testing.T) { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { pwd := tc.pwd - out, ok, err := f.Detect(tc.in, pwd) + out, ok, err := f.DetectGetter(tc.in, pwd) if err != nil { t.Fatalf("err: %s", err) } @@ -99,7 +99,7 @@ func TestFileDetector_noPwd(t *testing.T) { f := new(FileGetter) for i, tc := range noPwdFileTests { - out, ok, err := f.Detect(tc.in, tc.pwd) + out, ok, err := f.DetectGetter(tc.in, tc.pwd) if err != nil != tc.err { t.Fatalf("%d: err: %s", i, err) } diff --git a/detect_gcs_test.go b/detect_gcs_test.go index 8f78e8a2a..cd99561ac 100644 --- a/detect_gcs_test.go +++ b/detect_gcs_test.go @@ -26,7 +26,7 @@ func TestGCSDetector(t *testing.T) { pwd := "/pwd" f := new(GCSGetter) for i, tc := range cases { - output, ok, err := f.Detect(tc.Input, pwd) + output, ok, err := f.DetectGetter(tc.Input, pwd) if err != nil { t.Fatalf("err: %s", err) } diff --git a/detect_git_test.go b/detect_git_test.go index 99af4141f..b50a44d6b 100644 --- a/detect_git_test.go +++ b/detect_git_test.go @@ -57,15 +57,11 @@ func TestGitDetector(t *testing.T) { pwd := "/pwd" for _, tc := range cases { t.Run(tc.Input, func(t *testing.T) { - output, ok, err := Detect(tc.Input, pwd, new(GitGetter)) + detector := NewGetterDetector([]Getter{new(GitGetter)}) + output, err := detector.Detect(tc.Input, pwd) if err != nil { t.Fatalf("unexpected error: %s", err) } - - if !ok { - t.Fatalf("%s url expected to valid", tc.Input) - } - if output != tc.Output { t.Errorf("wrong result\ninput: %s\ngot: %s\nwant: %s", tc.Input, output, tc.Output) } diff --git a/detect_github_test.go b/detect_github_test.go index 427e175ad..2b94bea80 100644 --- a/detect_github_test.go +++ b/detect_github_test.go @@ -29,7 +29,7 @@ func TestGitHubDetector(t *testing.T) { pwd := "/pwd" f := new(GitGetter) for i, tc := range cases { - output, ok, err := f.Detect(tc.Input, pwd) + output, ok, err := f.DetectGetter(tc.Input, pwd) if err != nil { t.Fatalf("err: %s", err) } diff --git a/detect_s3_test.go b/detect_s3_test.go index 015efbdcb..8981a9597 100644 --- a/detect_s3_test.go +++ b/detect_s3_test.go @@ -69,7 +69,7 @@ func TestS3Detector(t *testing.T) { pwd := "/pwd" f := new(S3Getter) for i, tc := range cases { - output, ok, err := f.Detect(tc.Input, pwd) + output, ok, err := f.DetectGetter(tc.Input, pwd) if err != nil { t.Fatalf("err: %s", err) } diff --git a/detect_test.go b/detector_test.go similarity index 77% rename from detect_test.go rename to detector_test.go index 42e02b199..426d0c732 100644 --- a/detect_test.go +++ b/detector_test.go @@ -10,58 +10,50 @@ func TestDetect(t *testing.T) { Input string Pwd string Output string - g Getter Err bool }{ - {"./foo", "/foo", "/foo/foo", new(FileGetter), false}, - //{"git::./foo", "/foo", "/foo/foo", new(GitGetter),false}, // TODO @sylviamoss understand this test. Is this a real situation? + {"./foo", "/foo", "/foo/foo", false}, + {"git::./foo", "/foo", "/foo/foo", false}, { "git::github.com/hashicorp/foo", "", "https://github.com/hashicorp/foo.git", - new(GitGetter), false, }, { "./foo//bar", "/foo", "/foo/foo//bar", - new(FileGetter), false, }, { "git::github.com/hashicorp/foo//bar", "", "https://github.com/hashicorp/foo.git//bar", - new(GitGetter), false, }, { "git::https://github.com/hashicorp/consul.git", "", "https://github.com/hashicorp/consul.git", - new(GitGetter), false, }, { "git::https://person@someothergit.com/foo/bar", // this "", "https://person@someothergit.com/foo/bar", - new(GitGetter), false, }, { "git::https://person@someothergit.com/foo/bar", // this "/bar", "https://person@someothergit.com/foo/bar", - new(GitGetter), false, }, { "./foo/archive//*", "/bar", "/bar/foo/archive//*", - new(FileGetter), false, }, @@ -70,34 +62,41 @@ func TestDetect(t *testing.T) { "git::ssh://git@my.custom.git/dir1/dir2", "", "ssh://git@my.custom.git/dir1/dir2", - new(GitGetter), false, }, { "git::git@my.custom.git:dir1/dir2", "/foo", "ssh://git@my.custom.git/dir1/dir2", - new(GitGetter), false, }, { "git::git@my.custom.git:dir1/dir2", "", "ssh://git@my.custom.git/dir1/dir2", - new(GitGetter), false, }, } for i, tc := range cases { + httpGetter := &HttpGetter{ + Netrc: true, + } + Getters = []Getter{ + new(GitGetter), + new(HgGetter), + new(S3Getter), + new(GCSGetter), + new(FileGetter), + new(SmbGetter), + httpGetter, + } t.Run(fmt.Sprintf("%d %s", i, tc.Input), func(t *testing.T) { - output, ok, err := Detect(tc.Input, tc.Pwd, tc.g) + detector := NewGetterDetector(Getters) + output, err := detector.Detect(tc.Input, tc.Pwd) if err != nil != tc.Err { t.Fatalf("%d: bad err: %s", i, err) } - if !ok { - t.Fatalf("%s url expected to valid", tc.Input) - } if output != tc.Output { t.Fatalf("%d: bad output: %s\nexpected: %s", i, output, tc.Output) } diff --git a/get.go b/get.go index 78723cc56..307317315 100644 --- a/get.go +++ b/get.go @@ -45,7 +45,13 @@ type Getter interface { // Detect will detect whether the string matches a known pattern to // turn it into a proper URL. - Detect(string, string) (string, bool, error) + Detect(string, string) (string, []Getter, error) + + DetectGetter(src string, pwd string) (string, bool, error) + + SetNext(next Getter) + + Next() Getter ValidScheme(string) bool } @@ -79,7 +85,6 @@ func init() { new(FileGetter), new(SmbGetter), httpGetter, - httpGetter, } } diff --git a/get_detector.go b/get_detector.go new file mode 100644 index 000000000..fd3959504 --- /dev/null +++ b/get_detector.go @@ -0,0 +1,157 @@ +package getter + +import ( + "fmt" + "github.com/hashicorp/go-getter/helper/url" + "path/filepath" +) + +type GetterDetector struct { + getters []Getter +} + +func NewGetterDetector(getters []Getter) *GetterDetector { + for i, g := range getters { + if i == len(getters)-1 { + break + } + g.SetNext(getters[i+1]) + } + return &GetterDetector{getters} +} + +func (g *GetterDetector) Detect(src, pwd string) (string, error) { + // Start chain of detection to allow detecting multiple getters for a src + result, getters, err := g.getters[0].Detect(src, pwd) + g.getters = getters + return result, err +} + +//func (g *GetterDetector) Mode(ctx context.Context, u *url.URL) (Mode, error) { +// var result *multierror.Error +// for _, getter := range g.getters { +// mode, err := getter.Mode(ctx, u) +// if err == nil { +// // will return the first mode that is found and +// // the current getter will be used for downloading the object +// g.getters = []Getter{getter} +// return mode, nil +// } +// result = multierror.Append(result, err) +// } +// return 0, result +//} +// +//func (g *GetterDetector) Get(ctx context.Context, req *Request) error { +// // Mode will always return the first mode that is found successfully +// // At this point, the getters list will always contain only 1 getter +// return g.getters[0].Get(ctx, req) +//} +// +//func (g *GetterDetector) GetFile(ctx context.Context, req *Request) error { +// // Mode will always return the first mode that is found successfully +// // At this point, the getters list will always contain only 1 getter +// return g.getters[0].GetFile(ctx, req) +//} + +// Detect turns a source string into another source string if it is +// detected to be of a known pattern. +// +// The third parameter should be the list of detectors to use in the +// order to try them. If you don't want to configure this, just use +// the global Detectors variable. +// +// This is safe to be called with an already valid source string: Detect +// will just return it. +func Detect(src string, pwd string, g Getter) (string, []Getter, error) { + gList := []Getter{g} + getForce, getSrc := getForcedGetter(src) + isForcedGetter := getForce != "" && g.ValidScheme(getForce) + + // Separate out the subdir if there is one, we don't pass that to detect + getSrc, subDir := SourceDirSubdir(getSrc) + + u, err := url.Parse(getSrc) + if err == nil && u.Scheme != "" { + if !isForcedGetter && !g.ValidScheme(u.Scheme) { + // Not forced and no valid scheme + // Keep going through the chain to find valid getters for this scheme + return tryNextGetter(src, pwd, g) + } + if !isForcedGetter && g.ValidScheme(u.Scheme) { + // Not forced but a valid scheme for current getter + // Keep going through the chain to find other valid getters for this scheme + _, gs, err := tryNextGetter(src, pwd, g) + if gs != nil && err == nil { + // append this getter to the list of valid getters + return getSrc, append(gList, gs...), nil + } + return getSrc, gList, nil + } + if isForcedGetter { + // With a forced getter and another scheme, we want to try only the force getter + return getSrc, gList, nil + } + } + + result, ok, err := g.DetectGetter(getSrc, pwd) + if err != nil { + return "", nil, err + } + if !ok { + // Try next of the chain + r, gs, err := tryNextGetter(src, pwd, g) + if r == "" && err == nil { + r = getSrc + } + if isForcedGetter && err == nil { + // Remove forced getter from the path + _, r = getForcedGetter(r) + return r, gList, nil + } + return r, gs, err + } + + result, detectSubdir := SourceDirSubdir(result) + + // If we have a subdir from the detection, then prepend it to our + // requested subdir. + if detectSubdir != "" { + if subDir != "" { + subDir = filepath.Join(detectSubdir, subDir) + } else { + subDir = detectSubdir + } + } + + if subDir != "" { + u, err := url.Parse(result) + if err != nil { + return "", gList, fmt.Errorf("Error parsing URL: %s", err) + } + u.Path += "//" + subDir + + // a subdir may contain wildcards, but in order to support them we + // have to ensure the path isn't escaped. + u.RawPath = u.Path + + result = u.String() + } + + if getForce != "" && !isForcedGetter { + // If there's a forced getter and it's not the current one + // We don't append current getter to the list and try next getter + result = fmt.Sprintf("%s::%s", getForce, result) + _, gs, err := tryNextGetter(result, pwd, g) + return result, gs, err + } + // no forced getter, no scheme, and this is valid by detection + return result, gList, nil +} + +func tryNextGetter(src string, pwd string, g Getter) (string, []Getter, error) { + if g.Next() != nil { + return g.Next().Detect(src, pwd) + } + return "", nil, nil +} diff --git a/get_file.go b/get_file.go index aeb82bbd8..6ee35be15 100644 --- a/get_file.go +++ b/get_file.go @@ -12,6 +12,7 @@ import ( // FileGetter is a Getter implementation that will download a module from // a file scheme. type FileGetter struct { + next Getter } func (g *FileGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -150,7 +151,7 @@ func (g *FileGetter) GetFile(ctx context.Context, req *Request) error { return err } -func (g *FileGetter) Detect(src, pwd string) (string, bool, error) { +func (g *FileGetter) DetectGetter(src string, pwd string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -215,3 +216,15 @@ func fmtFileURL(path string) string { } return path } + +func (g *FileGetter) Detect(src, pwd string) (string, []Getter, error) { + return Detect(src, pwd, g) +} + +func (g *FileGetter) Next() Getter { + return g.next +} + +func (g *FileGetter) SetNext(next Getter) { + g.next = next +} diff --git a/get_gcs.go b/get_gcs.go index daf9d283f..e84737ab5 100644 --- a/get_gcs.go +++ b/get_gcs.go @@ -15,6 +15,7 @@ import ( // GCSGetter is a Getter implementation that will download a module from // a GCS bucket. type GCSGetter struct { + next Getter } func (g *GCSGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -165,7 +166,7 @@ func (g *GCSGetter) parseURL(u *url.URL) (bucket, path string, err error) { return } -func (g *GCSGetter) Detect(src, _ string) (string, bool, error) { +func (g *GCSGetter) DetectGetter(src, _ string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -206,3 +207,15 @@ func (g *GCSGetter) detectHTTP(src string) (string, bool, error) { func (g *GCSGetter) ValidScheme(scheme string) bool { return scheme == "gcs" } + +func (g *GCSGetter) Detect(src, pwd string) (string, []Getter, error) { + return Detect(src, pwd, g) +} + +func (g *GCSGetter) Next() Getter { + return g.next +} + +func (g *GCSGetter) SetNext(next Getter) { + g.next = next +} diff --git a/get_git.go b/get_git.go index f167c96f9..a2d7caea8 100644 --- a/get_git.go +++ b/get_git.go @@ -25,6 +25,7 @@ import ( // GitGetter is a Getter implementation that will download a module from // a git repository. type GitGetter struct { + next Getter } var defaultBranchRegexp = regexp.MustCompile(`\s->\sorigin/(.*)`) @@ -316,7 +317,7 @@ func checkGitVersion(min string) error { return nil } -func (g *GitGetter) Detect(src, _ string) (string, bool, error) { +func (g *GitGetter) DetectGetter(src, _ string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -424,3 +425,15 @@ func detectGitHub(src string) (string, bool, error) { func (g *GitGetter) ValidScheme(scheme string) bool { return scheme == "git" || scheme == "ssh" } + +func (g *GitGetter) Detect(src, pwd string) (string, []Getter, error) { + return Detect(src, pwd, g) +} + +func (g *GitGetter) Next() Getter { + return g.next +} + +func (g *GitGetter) SetNext(next Getter) { + g.next = next +} diff --git a/get_git_test.go b/get_git_test.go index cbfa0036d..1e9e20eb2 100644 --- a/get_git_test.go +++ b/get_git_test.go @@ -493,12 +493,6 @@ func TestGitGetter_sshSCPStyleInvalidScheme(t *testing.T) { if err == nil { t.Fatalf("get succeeded; want error") } - - got := err.Error() - want1, want2 := `invalid source string`, `invalid port ":hashicorp"` - if !(strings.Contains(got, want1) || strings.Contains(got, want2)) { - t.Fatalf("wrong error\ngot: %s\nwant: %q or %q", got, want1, want2) - } } func TestGitGetter_submodule(t *testing.T) { diff --git a/get_hg.go b/get_hg.go index e81692a70..3bbab53ec 100644 --- a/get_hg.go +++ b/get_hg.go @@ -15,6 +15,7 @@ import ( // HgGetter is a Getter implementation that will download a module from // a Mercurial repository. type HgGetter struct { + next Getter } func (g *HgGetter) Mode(ctx context.Context, _ *url.URL) (Mode, error) { @@ -124,7 +125,7 @@ func (g *HgGetter) update(ctx context.Context, dst string, u *url.URL, rev strin return getRunCommand(cmd) } -func (g *HgGetter) Detect(src, _ string) (string, bool, error) { +func (g *HgGetter) DetectGetter(src, _ string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -160,3 +161,15 @@ func fixWindowsDrivePath(u *url.URL) bool { return runtime.GOOS == "windows" && u.Scheme == "file" && len(u.Path) > 1 && u.Path[0] != '/' && u.Path[1] == ':' } + +func (g *HgGetter) Detect(src, pwd string) (string, []Getter, error) { + return Detect(src, pwd, g) +} + +func (g *HgGetter) Next() Getter { + return g.next +} + +func (g *HgGetter) SetNext(next Getter) { + g.next = next +} diff --git a/get_http.go b/get_http.go index d4f1aaf74..2f056b80f 100644 --- a/get_http.go +++ b/get_http.go @@ -48,6 +48,8 @@ type HttpGetter struct { // and as such it needs to be initialized before use, via something like // make(http.Header). Header http.Header + + next Getter } func (g *HttpGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -319,7 +321,7 @@ func charsetReader(charset string, input io.Reader) (io.Reader, error) { } } -func (g *HttpGetter) Detect(src, _ string) (string, bool, error) { +func (g *HttpGetter) DetectGetter(src, _ string) (string, bool, error) { u, err := url.Parse(src) if err == nil && u.Scheme != "" && (u.Scheme == "http" || u.Scheme == "https") { // Valid URL @@ -331,3 +333,15 @@ func (g *HttpGetter) Detect(src, _ string) (string, bool, error) { func (g *HttpGetter) ValidScheme(scheme string) bool { return scheme == "http" || scheme == "https" } + +func (g *HttpGetter) Detect(src, pwd string) (string, []Getter, error) { + return Detect(src, pwd, g) +} + +func (g *HttpGetter) Next() Getter { + return g.next +} + +func (g *HttpGetter) SetNext(next Getter) { + g.next = next +} diff --git a/get_mock.go b/get_mock.go index 32f0f915d..7714152a4 100644 --- a/get_mock.go +++ b/get_mock.go @@ -52,10 +52,34 @@ func (g *MockGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return ModeFile, nil } -func (g *MockGetter) Detect(src, _ string) (string, bool, error) { +func (g *MockGetter) DetectGetter(src, pwd string) (string, bool, error) { + if g.Proxy != nil { + return g.Proxy.DetectGetter(src, pwd) + } return "", true, nil } func (g *MockGetter) ValidScheme(scheme string) bool { + if g.Proxy != nil { + return g.Proxy.ValidScheme(scheme) + } return true } + +func (g *MockGetter) Detect(src, pwd string) (string, []Getter, error) { + if g.Proxy != nil { + detect, _, err := g.Proxy.Detect(src, pwd) + return detect, []Getter{g}, err + } + return src, []Getter{g}, nil +} + +func (g *MockGetter) Next() Getter { + if g.Proxy != nil { + return g.Proxy.Next() + } + return nil +} + +func (g *MockGetter) SetNext(next Getter) { +} diff --git a/get_s3.go b/get_s3.go index 87cfd6410..0feb7064c 100644 --- a/get_s3.go +++ b/get_s3.go @@ -19,6 +19,7 @@ import ( // S3Getter is a Getter implementation that will download a module from // a S3 bucket. type S3Getter struct { + next Getter } func (g *S3Getter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -271,7 +272,7 @@ func (g *S3Getter) parseUrl(u *url.URL) (region, bucket, path, version string, c return } -func (g *S3Getter) Detect(src, _ string) (string, bool, error) { +func (g *S3Getter) DetectGetter(src, _ string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -330,3 +331,15 @@ func (g *S3Getter) detectVhostStyle(region, bucket string, parts []string) (stri func (g *S3Getter) ValidScheme(scheme string) bool { return scheme == "s3" } + +func (g *S3Getter) Detect(src, pwd string) (string, []Getter, error) { + return Detect(src, pwd, g) +} + +func (g *S3Getter) Next() Getter { + return g.next +} + +func (g *S3Getter) SetNext(next Getter) { + g.next = next +} diff --git a/get_smb.go b/get_smb.go index 054b17cff..057070385 100644 --- a/get_smb.go +++ b/get_smb.go @@ -16,6 +16,7 @@ import ( // SmbGetter is a Getter implementation that will download a module from // a shared folder using smbclient cli for Unix and local file system for Windows. type SmbGetter struct { + next Getter } func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -282,7 +283,7 @@ func (g *SmbGetter) runSmbClientCommand(dst string, args []string) (string, erro return buf.String(), fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) } -func (g *SmbGetter) Detect(src, pwd string) (string, bool, error) { +func (g *SmbGetter) DetectGetter(src, pwd string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -300,6 +301,18 @@ func (g *SmbGetter) ValidScheme(scheme string) bool { return scheme == "smb" } +func (g *SmbGetter) Detect(src, pwd string) (string, []Getter, error) { + return Detect(src, pwd, g) +} + +func (g *SmbGetter) Next() Getter { + return g.next +} + +func (g *SmbGetter) SetNext(next Getter) { + g.next = next +} + type smbPathError struct { Path string } From fe63e444e77039851c9436c42a021e2421168636 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 4 May 2020 16:39:37 +0200 Subject: [PATCH 078/109] Solve todos and improve comments --- client.go | 15 +++++++---- detect_git_test.go | 7 ----- get_detector.go | 66 ++++++++++++++-------------------------------- 3 files changed, 30 insertions(+), 58 deletions(-) diff --git a/client.go b/client.go index de898f62f..d6eba4437 100644 --- a/client.go +++ b/client.go @@ -46,12 +46,13 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { req.Mode = ModeAny } - executor := NewGetterDetector(c.Getters) + detector := NewGetterDetector(c.Getters) - // Loop over Getters in priority order. + // Run over the chain of detection to get a list of possible Getters. // Some urls can be supported by more than one Getter and we want to make sure - // the best getter option will try to download first - src, err := executor.Detect(req.Src, req.Pwd) + // the best getter option will try to download first. + // The Getters slice must be in priority order. + src, err := detector.Detect(req.Src, req.Pwd) if err != nil { return nil, err } @@ -138,10 +139,12 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { q.Del("checksum") req.u.RawQuery = q.Encode() + // From now one, each possible getter will + // try to run the rest of the logic var modeErr *multierror.Error var getFileErr *multierror.Error var getErr *multierror.Error - for _, g := range executor.getters { + for _, g := range detector.getters { if req.Mode == ModeAny { // Ask the getter which client mode to use req.Mode, err = g.Mode(ctx, req.u) @@ -258,6 +261,8 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { return &GetResult{req.Dst}, nil } + // If there's an getErr or getFileErr, we can ignore any modeErr because is + // going to be a getter that didn't get to far on downloading the artifact if getErr != nil { err = fmt.Errorf("error downloading '%s': %s", req.Src, getErr) } diff --git a/detect_git_test.go b/detect_git_test.go index b50a44d6b..4103fe7e3 100644 --- a/detect_git_test.go +++ b/detect_git_test.go @@ -42,13 +42,6 @@ func TestGitDetector(t *testing.T) { "ssh://git@github.xyz.com/org/project.git//module/a?ref=test-branch", }, { - // Already in the canonical form, so no rewriting required - // When the ssh: protocol is used explicitly, we recognize it as - // URL form rather than SCP-like form, so the part after the colon - // is a port number, not part of the path. - - // TODO @sylviamoss rewrite comment - // No need to set git scheme anymore "git::ssh://git@git.example.com:2222/hashicorp/foo.git", "ssh://git@git.example.com:2222/hashicorp/foo.git", }, diff --git a/get_detector.go b/get_detector.go index fd3959504..264f3b0f3 100644 --- a/get_detector.go +++ b/get_detector.go @@ -6,10 +6,13 @@ import ( "path/filepath" ) +// GetterDetector detects the possible Getters in the list of available Getters to download an artifact type GetterDetector struct { + // List of possible getters that will be used for trying to download the artifact getters []Getter } +// NewGetterDetector creates a chain of Getters that will be used for detection func NewGetterDetector(getters []Getter) *GetterDetector { for i, g := range getters { if i == len(getters)-1 { @@ -21,48 +24,22 @@ func NewGetterDetector(getters []Getter) *GetterDetector { } func (g *GetterDetector) Detect(src, pwd string) (string, error) { - // Start chain of detection to allow detecting multiple getters for a src - result, getters, err := g.getters[0].Detect(src, pwd) - g.getters = getters - return result, err + // Start chain of detection to allow detecting multiple getters for an object + if len(g.getters) > 0 { + result, getters, err := g.getters[0].Detect(src, pwd) + g.getters = getters + return result, err + } + return "", fmt.Errorf("couldn't find any available getter") } -//func (g *GetterDetector) Mode(ctx context.Context, u *url.URL) (Mode, error) { -// var result *multierror.Error -// for _, getter := range g.getters { -// mode, err := getter.Mode(ctx, u) -// if err == nil { -// // will return the first mode that is found and -// // the current getter will be used for downloading the object -// g.getters = []Getter{getter} -// return mode, nil -// } -// result = multierror.Append(result, err) -// } -// return 0, result -//} -// -//func (g *GetterDetector) Get(ctx context.Context, req *Request) error { -// // Mode will always return the first mode that is found successfully -// // At this point, the getters list will always contain only 1 getter -// return g.getters[0].Get(ctx, req) -//} -// -//func (g *GetterDetector) GetFile(ctx context.Context, req *Request) error { -// // Mode will always return the first mode that is found successfully -// // At this point, the getters list will always contain only 1 getter -// return g.getters[0].GetFile(ctx, req) -//} - -// Detect turns a source string into another source string if it is -// detected to be of a known pattern. -// -// The third parameter should be the list of detectors to use in the -// order to try them. If you don't want to configure this, just use -// the global Detectors variable. +// Detect is a shared method used by all of the Getters to validate if +// a source string is detected to be of a known pattern, +// and to transform it to a known pattern when necessary. // -// This is safe to be called with an already valid source string: Detect -// will just return it. +// This is the logic of the getters chain of detection. +// When a getter detects or not a valid source string, it will +// call the next getter that will then use this method to do the same. func Detect(src string, pwd string, g Getter) (string, []Getter, error) { gList := []Getter{g} getForce, getSrc := getForcedGetter(src) @@ -74,7 +51,7 @@ func Detect(src string, pwd string, g Getter) (string, []Getter, error) { u, err := url.Parse(getSrc) if err == nil && u.Scheme != "" { if !isForcedGetter && !g.ValidScheme(u.Scheme) { - // Not forced and no valid scheme + // Not forced getter and not valid scheme // Keep going through the chain to find valid getters for this scheme return tryNextGetter(src, pwd, g) } @@ -101,11 +78,8 @@ func Detect(src string, pwd string, g Getter) (string, []Getter, error) { if !ok { // Try next of the chain r, gs, err := tryNextGetter(src, pwd, g) - if r == "" && err == nil { - r = getSrc - } if isForcedGetter && err == nil { - // Remove forced getter from the path + // Remove any forced getter from the path _, r = getForcedGetter(r) return r, gList, nil } @@ -145,7 +119,7 @@ func Detect(src string, pwd string, g Getter) (string, []Getter, error) { _, gs, err := tryNextGetter(result, pwd, g) return result, gs, err } - // no forced getter, no scheme, and this is valid by detection + // this is valid by detection return result, gList, nil } @@ -153,5 +127,5 @@ func tryNextGetter(src string, pwd string, g Getter) (string, []Getter, error) { if g.Next() != nil { return g.Next().Detect(src, pwd) } - return "", nil, nil + return src, nil, nil } From bed918b2d1892a2556fa11fa6c7e1e171aefe5b6 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 5 May 2020 14:16:04 +0200 Subject: [PATCH 079/109] Add smb windows getter --- get_detector.go => detector.go | 0 get.go | 7 ++++--- get_smb_win.go | 27 ++++++++++++++++++++------- 3 files changed, 24 insertions(+), 10 deletions(-) rename get_detector.go => detector.go (100%) diff --git a/get_detector.go b/detector.go similarity index 100% rename from get_detector.go rename to detector.go diff --git a/get.go b/get.go index 307317315..0faa276ee 100644 --- a/get.go +++ b/get.go @@ -45,11 +45,11 @@ type Getter interface { // Detect will detect whether the string matches a known pattern to // turn it into a proper URL. - Detect(string, string) (string, []Getter, error) + DetectGetter(string, string) (string, bool, error) - DetectGetter(src string, pwd string) (string, bool, error) + Detect(string, string) (string, []Getter, error) - SetNext(next Getter) + SetNext(Getter) Next() Getter @@ -84,6 +84,7 @@ func init() { new(GCSGetter), new(FileGetter), new(SmbGetter), + new(SmbWindowsGetter), httpGetter, } } diff --git a/get_smb_win.go b/get_smb_win.go index d024b2b65..50d600d5e 100644 --- a/get_smb_win.go +++ b/get_smb_win.go @@ -11,10 +11,11 @@ import ( // SmbGetter is a Getter implementation that will download a module from // a shared folder using smbclient cli for Unix and local file system for Windows. -type SmbGetterWindows struct { +type SmbWindowsGetter struct { + next Getter } -func (g *SmbGetterWindows) Mode(ctx context.Context, u *url.URL) (Mode, error) { +func (g *SmbWindowsGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.Host == "" || u.Path == "" { return 0, new(smbPathError) } @@ -28,7 +29,7 @@ func (g *SmbGetterWindows) Mode(ctx context.Context, u *url.URL) (Mode, error) { return 0, nil } -func (g *SmbGetterWindows) Get(ctx context.Context, req *Request) error { +func (g *SmbWindowsGetter) Get(ctx context.Context, req *Request) error { if req.u.Host == "" || req.u.Path == "" { return new(smbPathError) } @@ -42,7 +43,7 @@ func (g *SmbGetterWindows) Get(ctx context.Context, req *Request) error { return nil } -func (g *SmbGetterWindows) GetFile(ctx context.Context, req *Request) error { +func (g *SmbWindowsGetter) GetFile(ctx context.Context, req *Request) error { if req.u.Host == "" || req.u.Path == "" { return new(smbPathError) } @@ -56,12 +57,12 @@ func (g *SmbGetterWindows) GetFile(ctx context.Context, req *Request) error { return nil } -func (g *SmbGetterWindows) Detect(src, pwd string) (string, bool, error) { +func (g *SmbWindowsGetter) DetectGetter(src, pwd string) (string, bool, error) { if len(src) == 0 { return "", false, nil } - // Don't even try SmbGetterWindows if is not Windows + // Don't even try SmbWindowsGetter if is not Windows if runtime.GOOS != "windows" { return "", false, nil } @@ -85,6 +86,18 @@ func windowsSmbPath(path string) bool { return runtime.GOOS == "windows" && (strings.HasPrefix(path, "\\\\") || strings.HasPrefix(path, "//")) } -func (g *SmbGetterWindows) ValidScheme(scheme string) bool { +func (g *SmbWindowsGetter) ValidScheme(scheme string) bool { return scheme == "smb" } + +func (g *SmbWindowsGetter) Detect(src, pwd string) (string, []Getter, error) { + return Detect(src, pwd, g) +} + +func (g *SmbWindowsGetter) Next() Getter { + return g.next +} + +func (g *SmbWindowsGetter) SetNext(next Getter) { + g.next = next +} From f0c903b24a01a96341f2efe3db36cd577c029d06 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 5 May 2020 15:40:48 +0200 Subject: [PATCH 080/109] Add missing next to detector --- detector.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/detector.go b/detector.go index 264f3b0f3..541a6112a 100644 --- a/detector.go +++ b/detector.go @@ -119,7 +119,12 @@ func Detect(src string, pwd string, g Getter) (string, []Getter, error) { _, gs, err := tryNextGetter(result, pwd, g) return result, gs, err } - // this is valid by detection + + // this is valid by getter detection + r, gs, err := tryNextGetter(result, pwd, g) + if err != nil && gs != nil { + return r, append(gList, gs...), nil + } return result, gList, nil } From 2bd83fda38cf0bc3acfa85c3e74c6918c231696b Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 5 May 2020 17:46:03 +0200 Subject: [PATCH 081/109] Update smbmountgetter --- detector_test.go | 2 +- get.go | 4 +- get_file.go | 6 - get_smb_mount.go | 233 +++++++++++++++++++++++ get_smb_win.go | 103 ---------- get_smb.go => get_smbclient.go | 36 ++-- get_smb_test.go => get_smbclient_test.go | 8 +- source.go | 5 - 8 files changed, 258 insertions(+), 139 deletions(-) create mode 100644 get_smb_mount.go delete mode 100644 get_smb_win.go rename get_smb.go => get_smbclient.go (84%) rename get_smb_test.go => get_smbclient_test.go (98%) diff --git a/detector_test.go b/detector_test.go index 426d0c732..b04765651 100644 --- a/detector_test.go +++ b/detector_test.go @@ -88,7 +88,7 @@ func TestDetect(t *testing.T) { new(S3Getter), new(GCSGetter), new(FileGetter), - new(SmbGetter), + new(SmbClientGetter), httpGetter, } t.Run(fmt.Sprintf("%d %s", i, tc.Input), func(t *testing.T) { diff --git a/get.go b/get.go index 0faa276ee..8a6b9eae7 100644 --- a/get.go +++ b/get.go @@ -83,8 +83,8 @@ func init() { new(S3Getter), new(GCSGetter), new(FileGetter), - new(SmbGetter), - new(SmbWindowsGetter), + new(SmbClientGetter), + new(SmbMountGetter), httpGetter, } } diff --git a/get_file.go b/get_file.go index 6ee35be15..05db0966a 100644 --- a/get_file.go +++ b/get_file.go @@ -196,12 +196,6 @@ func (g *FileGetter) DetectGetter(src string, pwd string) (string, bool, error) src = filepath.Join(pwd, src) } - if windowsSmbPath(src) { - // This is a valid smb path for Windows and will be checked in the SmbGetter - // by the file system using the FileGetter, if available. - return src, false, nil - } - return fmtFileURL(src), true, nil } diff --git a/get_smb_mount.go b/get_smb_mount.go new file mode 100644 index 000000000..15cb1bda8 --- /dev/null +++ b/get_smb_mount.go @@ -0,0 +1,233 @@ +package getter + +import ( + "context" + "fmt" + "net/url" + "os" + "path/filepath" + "regexp" + "runtime" + "strings" +) + +// SmbMountGetter is a Getter implementation that will download a module from +// a shared folder using the file system. +type SmbMountGetter struct { + next Getter +} + +func (g *SmbMountGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { + if u.Host == "" || u.Path == "" { + return 0, new(smbPathError) + } + + prefix, path := g.findPrefixAndPath(u) + path = prefix + path + + if u.RawPath != "" { + path = u.RawPath + } + + fi, err := os.Stat(path) + if err != nil { + return 0, err + } + + // Check if the source is a directory. + if fi.IsDir() { + return ModeDir, nil + } + + return ModeFile, nil +} + +func (g *SmbMountGetter) Get(ctx context.Context, req *Request) error { + if req.u.Host == "" || req.u.Path == "" { + return new(smbPathError) + } + + prefix, path := g.findPrefixAndPath(req.u) + path = prefix + path + + if req.u.RawPath != "" { + path = req.u.RawPath + } + + // The source path must exist and be a directory to be usable. + if fi, err := os.Stat(path); err != nil { + return fmt.Errorf("source path error: %s", err) + } else if !fi.IsDir() { + return fmt.Errorf("source path must be a directory") + } + + fi, err := os.Lstat(req.Dst) + if err != nil && !os.IsNotExist(err) { + return err + } + + if req.Inplace { + req.Dst = path + return nil + } + + // If the destination already exists, it must be a symlink + if err == nil { + mode := fi.Mode() + if mode&os.ModeSymlink == 0 { + return fmt.Errorf("destination exists and is not a symlink") + } + + // Remove the destination + if err := os.Remove(req.Dst); err != nil { + return err + } + } + + // Create all the parent directories + if err := os.MkdirAll(filepath.Dir(req.Dst), 0755); err != nil { + return err + } + + return SymlinkAny(path, req.Dst) +} + +func (g *SmbMountGetter) GetFile(ctx context.Context, req *Request) error { + if req.u.Host == "" || req.u.Path == "" { + return new(smbPathError) + } + + prefix, path := g.findPrefixAndPath(req.u) + path = prefix + path + + if req.u.RawPath != "" { + path = req.u.RawPath + } + + // The source path must exist and be a file to be usable. + if fi, err := os.Stat(path); err != nil { + return fmt.Errorf("source path error: %s", err) + } else if fi.IsDir() { + return fmt.Errorf("source path must be a file") + } + + if req.Inplace { + req.Dst = path + return nil + } + + _, err := os.Lstat(req.Dst) + if err != nil && !os.IsNotExist(err) { + return err + } + + // If the destination already exists, it must be a symlink + if err == nil { + // Remove the destination + if err := os.Remove(req.Dst); err != nil { + return err + } + } + + // Create all the parent directories + if err := os.MkdirAll(filepath.Dir(req.Dst), 0755); err != nil { + return err + } + + // If we're not copying, just symlink and we're done + if !req.Copy { + if err = os.Symlink(path, req.Dst); err == nil { + return err + } + lerr, ok := err.(*os.LinkError) + if !ok { + return err + } + switch lerr.Err { + case ErrUnauthorized: + // On windows this means we don't have + // symlink privilege, let's + // fallback to a copy to avoid an error. + break + default: + return err + } + } + + // Copy + srcF, err := os.Open(path) + if err != nil { + return err + } + defer srcF.Close() + + dstF, err := os.Create(req.Dst) + if err != nil { + return err + } + defer dstF.Close() + + _, err = Copy(ctx, dstF, srcF) + return err +} + +func (g *SmbMountGetter) findPrefixAndPath(u *url.URL) (string, string) { + var prefix, path string + switch runtime.GOOS { + case "windows": + prefix = string(os.PathSeparator) + string(os.PathSeparator) + path = filepath.Join(u.Host, u.Path) + case "darwin": + prefix = string(os.PathSeparator) + path = filepath.Join("Volumes", u.Path) + case "linux": + prefix = string(os.PathSeparator) + share := g.findShare(u) + pwd := fmt.Sprintf("run/user/1000/gvfs/smb-share:server=%s,share=%s", u.Host, share) + path = filepath.Join(pwd, u.Path) + } + return prefix, path +} + +func (g *SmbMountGetter) findShare(u *url.URL) string { + // Get shared directory + path := strings.TrimPrefix(u.Path, "/") + splt := regexp.MustCompile(`/`) + directories := splt.Split(path, 2) + + if len(directories) > 0 { + return directories[0] + } + + return "." +} + +func (g *SmbMountGetter) DetectGetter(src, pwd string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + u, err := url.Parse(src) + if err == nil && u.Scheme == "smb" { + // Valid URL + return src, true, nil + } + + return "", false, nil +} + +func (g *SmbMountGetter) ValidScheme(scheme string) bool { + return scheme == "smb" +} + +func (g *SmbMountGetter) Detect(src, pwd string) (string, []Getter, error) { + return Detect(src, pwd, g) +} + +func (g *SmbMountGetter) Next() Getter { + return g.next +} + +func (g *SmbMountGetter) SetNext(next Getter) { + g.next = next +} diff --git a/get_smb_win.go b/get_smb_win.go deleted file mode 100644 index 50d600d5e..000000000 --- a/get_smb_win.go +++ /dev/null @@ -1,103 +0,0 @@ -package getter - -import ( - "context" - "net/url" - "os" - "path/filepath" - "runtime" - "strings" -) - -// SmbGetter is a Getter implementation that will download a module from -// a shared folder using smbclient cli for Unix and local file system for Windows. -type SmbWindowsGetter struct { - next Getter -} - -func (g *SmbWindowsGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { - if u.Host == "" || u.Path == "" { - return 0, new(smbPathError) - } - - if runtime.GOOS == "windows" { - prefix := string(os.PathSeparator) + string(os.PathSeparator) - u.Path = prefix + filepath.Join(u.Host, u.Path) - return new(FileGetter).Mode(ctx, u) - } - - return 0, nil -} - -func (g *SmbWindowsGetter) Get(ctx context.Context, req *Request) error { - if req.u.Host == "" || req.u.Path == "" { - return new(smbPathError) - } - - if runtime.GOOS == "windows" { - prefix := string(os.PathSeparator) + string(os.PathSeparator) - req.u.Path = prefix + filepath.Join(req.u.Host, req.u.Path) - return new(FileGetter).Get(ctx, req) - } - - return nil -} - -func (g *SmbWindowsGetter) GetFile(ctx context.Context, req *Request) error { - if req.u.Host == "" || req.u.Path == "" { - return new(smbPathError) - } - - if runtime.GOOS == "windows" { - prefix := string(os.PathSeparator) + string(os.PathSeparator) - req.u.Path = prefix + filepath.Join(req.u.Host, req.u.Path) - return new(FileGetter).GetFile(ctx, req) - } - - return nil -} - -func (g *SmbWindowsGetter) DetectGetter(src, pwd string) (string, bool, error) { - if len(src) == 0 { - return "", false, nil - } - - // Don't even try SmbWindowsGetter if is not Windows - if runtime.GOOS != "windows" { - return "", false, nil - } - - u, err := url.Parse(src) - if err == nil && u.Scheme == "smb" { - // Valid URL - return src, true, nil - } - - if windowsSmbPath(src) { - // This is a valid smb path for Windows and will be checked in the SmbGetter - // by the file system using the FileGetter, if available. - return filepath.ToSlash(src), true, nil - } - - return "", false, nil -} - -func windowsSmbPath(path string) bool { - return runtime.GOOS == "windows" && (strings.HasPrefix(path, "\\\\") || strings.HasPrefix(path, "//")) -} - -func (g *SmbWindowsGetter) ValidScheme(scheme string) bool { - return scheme == "smb" -} - -func (g *SmbWindowsGetter) Detect(src, pwd string) (string, []Getter, error) { - return Detect(src, pwd, g) -} - -func (g *SmbWindowsGetter) Next() Getter { - return g.next -} - -func (g *SmbWindowsGetter) SetNext(next Getter) { - g.next = next -} diff --git a/get_smb.go b/get_smbclient.go similarity index 84% rename from get_smb.go rename to get_smbclient.go index 057070385..0752a851f 100644 --- a/get_smb.go +++ b/get_smbclient.go @@ -13,13 +13,13 @@ import ( "syscall" ) -// SmbGetter is a Getter implementation that will download a module from -// a shared folder using smbclient cli for Unix and local file system for Windows. -type SmbGetter struct { +// SmbClientGetter is a Getter implementation that will download a module from +// a shared folder using smbclient cli. +type SmbClientGetter struct { next Getter } -func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { +func (g *SmbClientGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.Host == "" || u.Path == "" { return 0, new(smbPathError) } @@ -32,7 +32,7 @@ func (g *SmbGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return 0, &smbGeneralError{err} } -func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { +func (g *SmbClientGetter) smbClientMode(u *url.URL) (Mode, error) { hostPath, filePath, err := g.findHostAndFilePath(u) if err != nil { return 0, err @@ -60,7 +60,7 @@ func (g *SmbGetter) smbClientMode(u *url.URL) (Mode, error) { return ModeFile, nil } -func (g *SmbGetter) Get(ctx context.Context, req *Request) error { +func (g *SmbClientGetter) Get(ctx context.Context, req *Request) error { if req.u.Host == "" || req.u.Path == "" { return new(smbPathError) } @@ -87,7 +87,7 @@ func (g *SmbGetter) Get(ctx context.Context, req *Request) error { return &smbGeneralError{err} } -func (g *SmbGetter) smbclientGet(req *Request) error { +func (g *SmbClientGetter) smbclientGet(req *Request) error { hostPath, directory, err := g.findHostAndFilePath(req.u) if err != nil { return err @@ -125,7 +125,7 @@ func (g *SmbGetter) smbclientGet(req *Request) error { return err } -func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { +func (g *SmbClientGetter) GetFile(ctx context.Context, req *Request) error { if req.u.Host == "" || req.u.Path == "" { return new(smbPathError) } @@ -152,7 +152,7 @@ func (g *SmbGetter) GetFile(ctx context.Context, req *Request) error { return &smbGeneralError{err} } -func (g *SmbGetter) smbclientGetFile(req *Request) error { +func (g *SmbClientGetter) smbclientGetFile(req *Request) error { hostPath, filePath, err := g.findHostAndFilePath(req.u) if err != nil { return err @@ -202,7 +202,7 @@ func (g *SmbGetter) smbclientGetFile(req *Request) error { return err } -func (g *SmbGetter) smbclientCmdArgs(used *url.Userinfo, hostPath string, fileDir string) (baseCmd []string) { +func (g *SmbClientGetter) smbclientCmdArgs(used *url.Userinfo, hostPath string, fileDir string) (baseCmd []string) { baseCmd = append(baseCmd, "-N") // Append auth user and password to baseCmd @@ -221,7 +221,7 @@ func (g *SmbGetter) smbclientCmdArgs(used *url.Userinfo, hostPath string, fileDi return baseCmd } -func (g *SmbGetter) findHostAndFilePath(u *url.URL) (string, string, error) { +func (g *SmbClientGetter) findHostAndFilePath(u *url.URL) (string, string, error) { // Host path hostPath := "//" + u.Host @@ -242,7 +242,7 @@ func (g *SmbGetter) findHostAndFilePath(u *url.URL) (string, string, error) { return hostPath, directories[1], nil } -func (g *SmbGetter) isDirectory(args []string, object string) (bool, error) { +func (g *SmbClientGetter) isDirectory(args []string, object string) (bool, error) { args = append(args, "-c") args = append(args, fmt.Sprintf("allinfo %s", object)) output, err := g.runSmbClientCommand("", args) @@ -255,7 +255,7 @@ func (g *SmbGetter) isDirectory(args []string, object string) (bool, error) { return strings.Contains(output, "attributes: D"), nil } -func (g *SmbGetter) runSmbClientCommand(dst string, args []string) (string, error) { +func (g *SmbClientGetter) runSmbClientCommand(dst string, args []string) (string, error) { cmd := exec.Command("smbclient", args...) if dst != "" { @@ -283,7 +283,7 @@ func (g *SmbGetter) runSmbClientCommand(dst string, args []string) (string, erro return buf.String(), fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) } -func (g *SmbGetter) DetectGetter(src, pwd string) (string, bool, error) { +func (g *SmbClientGetter) DetectGetter(src, pwd string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -297,19 +297,19 @@ func (g *SmbGetter) DetectGetter(src, pwd string) (string, bool, error) { return "", false, nil } -func (g *SmbGetter) ValidScheme(scheme string) bool { +func (g *SmbClientGetter) ValidScheme(scheme string) bool { return scheme == "smb" } -func (g *SmbGetter) Detect(src, pwd string) (string, []Getter, error) { +func (g *SmbClientGetter) Detect(src, pwd string) (string, []Getter, error) { return Detect(src, pwd, g) } -func (g *SmbGetter) Next() Getter { +func (g *SmbClientGetter) Next() Getter { return g.next } -func (g *SmbGetter) SetNext(next Getter) { +func (g *SmbClientGetter) SetNext(next Getter) { g.next = next } diff --git a/get_smb_test.go b/get_smbclient_test.go similarity index 98% rename from get_smb_test.go rename to get_smbclient_test.go index 516342e81..610322f5b 100644 --- a/get_smb_test.go +++ b/get_smbclient_test.go @@ -9,7 +9,7 @@ import ( ) func TestSmb_GetterImpl(t *testing.T) { - var _ Getter = new(SmbGetter) + var _ Getter = new(SmbClientGetter) } func TestSmb_GetterGet(t *testing.T) { @@ -97,7 +97,7 @@ func TestSmb_GetterGet(t *testing.T) { u: url, } - g := new(SmbGetter) + g := new(SmbClientGetter) err = g.Get(context.Background(), req) fail := err != nil @@ -214,7 +214,7 @@ func TestSmb_GetterGetFile(t *testing.T) { u: url, } - g := new(SmbGetter) + g := new(SmbClientGetter) err = g.GetFile(context.Background(), req) fail := err != nil @@ -300,7 +300,7 @@ func TestSmb_GetterMode(t *testing.T) { t.Fatalf("err: %s", err.Error()) } - g := new(SmbGetter) + g := new(SmbClientGetter) mode, err := g.Mode(context.Background(), url) fail := err != nil diff --git a/source.go b/source.go index bd0ddc1b2..23e70d77d 100644 --- a/source.go +++ b/source.go @@ -15,11 +15,6 @@ import ( // proto://dom.com/path//path2?q=p => proto://dom.com/path?q=p, "path2" // func SourceDirSubdir(src string) (string, string) { - if windowsSmbPath(src) { - // This is valid for smb path for Windows - return src, "" - } - // URL might contains another url in query parameters stop := len(src) if idx := strings.Index(src, "?"); idx > -1 { From 7f4c3d1cfa4df31f8f4c21e0200f2e834a9e57d7 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 5 May 2020 17:54:38 +0200 Subject: [PATCH 082/109] fix format --- get_smb_mount.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get_smb_mount.go b/get_smb_mount.go index 15cb1bda8..79d816f09 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -196,7 +196,7 @@ func (g *SmbMountGetter) findShare(u *url.URL) string { directories := splt.Split(path, 2) if len(directories) > 0 { - return directories[0] + return directories[0] } return "." From 4516534d57ee59459674c8f937d6ead8208a703f Mon Sep 17 00:00:00 2001 From: Moss Date: Wed, 6 May 2020 12:32:25 +0200 Subject: [PATCH 083/109] make chain a loop --- client.go | 8 +- detect_bitbucket_test.go | 2 +- detect_file_test.go | 4 +- detect_gcs_test.go | 2 +- detect_git_test.go | 3 +- detect_github_test.go | 2 +- detect_s3_test.go | 2 +- detector.go | 179 ++++++++++++++++----------------------- detector_test.go | 3 +- get.go | 8 +- get_file.go | 14 +-- get_gcs.go | 14 +-- get_git.go | 14 +-- get_hg.go | 14 +-- get_http.go | 14 +-- get_mock.go | 24 +----- get_s3.go | 14 +-- get_smb_mount.go | 14 +-- get_smbclient.go | 14 +-- 19 files changed, 94 insertions(+), 255 deletions(-) diff --git a/client.go b/client.go index d6eba4437..2c1de87dc 100644 --- a/client.go +++ b/client.go @@ -46,13 +46,11 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { req.Mode = ModeAny } - detector := NewGetterDetector(c.Getters) - - // Run over the chain of detection to get a list of possible Getters. + // Run over the getters to get a list of possible Getters. // Some urls can be supported by more than one Getter and we want to make sure // the best getter option will try to download first. // The Getters slice must be in priority order. - src, err := detector.Detect(req.Src, req.Pwd) + src, getters, err := Detect(req.Src, req.Pwd, c.Getters) if err != nil { return nil, err } @@ -144,7 +142,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { var modeErr *multierror.Error var getFileErr *multierror.Error var getErr *multierror.Error - for _, g := range detector.getters { + for _, g := range getters { if req.Mode == ModeAny { // Ask the getter which client mode to use req.Mode, err = g.Mode(ctx, req.u) diff --git a/detect_bitbucket_test.go b/detect_bitbucket_test.go index b04302b29..e9c4c256f 100644 --- a/detect_bitbucket_test.go +++ b/detect_bitbucket_test.go @@ -45,7 +45,7 @@ func TestBitBucketDetector(t *testing.T) { for i := 0; i < 3; i++ { var output string var ok bool - output, ok, err = tc.g.DetectGetter(tc.Input, pwd) + output, ok, err = tc.g.Detect(tc.Input, pwd) if err != nil { if strings.Contains(err.Error(), "invalid character") { continue diff --git a/detect_file_test.go b/detect_file_test.go index 5f100fee6..dbe794d4c 100644 --- a/detect_file_test.go +++ b/detect_file_test.go @@ -55,7 +55,7 @@ func TestFileDetector(t *testing.T) { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { pwd := tc.pwd - out, ok, err := f.DetectGetter(tc.in, pwd) + out, ok, err := f.Detect(tc.in, pwd) if err != nil { t.Fatalf("err: %s", err) } @@ -99,7 +99,7 @@ func TestFileDetector_noPwd(t *testing.T) { f := new(FileGetter) for i, tc := range noPwdFileTests { - out, ok, err := f.DetectGetter(tc.in, tc.pwd) + out, ok, err := f.Detect(tc.in, tc.pwd) if err != nil != tc.err { t.Fatalf("%d: err: %s", i, err) } diff --git a/detect_gcs_test.go b/detect_gcs_test.go index cd99561ac..8f78e8a2a 100644 --- a/detect_gcs_test.go +++ b/detect_gcs_test.go @@ -26,7 +26,7 @@ func TestGCSDetector(t *testing.T) { pwd := "/pwd" f := new(GCSGetter) for i, tc := range cases { - output, ok, err := f.DetectGetter(tc.Input, pwd) + output, ok, err := f.Detect(tc.Input, pwd) if err != nil { t.Fatalf("err: %s", err) } diff --git a/detect_git_test.go b/detect_git_test.go index 4103fe7e3..a62ea4f83 100644 --- a/detect_git_test.go +++ b/detect_git_test.go @@ -50,8 +50,7 @@ func TestGitDetector(t *testing.T) { pwd := "/pwd" for _, tc := range cases { t.Run(tc.Input, func(t *testing.T) { - detector := NewGetterDetector([]Getter{new(GitGetter)}) - output, err := detector.Detect(tc.Input, pwd) + output, _, err := Detect(tc.Input, pwd, []Getter{new(GitGetter)}) if err != nil { t.Fatalf("unexpected error: %s", err) } diff --git a/detect_github_test.go b/detect_github_test.go index 2b94bea80..427e175ad 100644 --- a/detect_github_test.go +++ b/detect_github_test.go @@ -29,7 +29,7 @@ func TestGitHubDetector(t *testing.T) { pwd := "/pwd" f := new(GitGetter) for i, tc := range cases { - output, ok, err := f.DetectGetter(tc.Input, pwd) + output, ok, err := f.Detect(tc.Input, pwd) if err != nil { t.Fatalf("err: %s", err) } diff --git a/detect_s3_test.go b/detect_s3_test.go index 8981a9597..015efbdcb 100644 --- a/detect_s3_test.go +++ b/detect_s3_test.go @@ -69,7 +69,7 @@ func TestS3Detector(t *testing.T) { pwd := "/pwd" f := new(S3Getter) for i, tc := range cases { - output, ok, err := f.DetectGetter(tc.Input, pwd) + output, ok, err := f.Detect(tc.Input, pwd) if err != nil { t.Fatalf("err: %s", err) } diff --git a/detector.go b/detector.go index 541a6112a..74c0f71e6 100644 --- a/detector.go +++ b/detector.go @@ -6,131 +6,94 @@ import ( "path/filepath" ) -// GetterDetector detects the possible Getters in the list of available Getters to download an artifact -type GetterDetector struct { - // List of possible getters that will be used for trying to download the artifact - getters []Getter -} - -// NewGetterDetector creates a chain of Getters that will be used for detection -func NewGetterDetector(getters []Getter) *GetterDetector { - for i, g := range getters { - if i == len(getters)-1 { - break - } - g.SetNext(getters[i+1]) - } - return &GetterDetector{getters} -} - -func (g *GetterDetector) Detect(src, pwd string) (string, error) { - // Start chain of detection to allow detecting multiple getters for an object - if len(g.getters) > 0 { - result, getters, err := g.getters[0].Detect(src, pwd) - g.getters = getters - return result, err - } - return "", fmt.Errorf("couldn't find any available getter") -} - -// Detect is a shared method used by all of the Getters to validate if +// Detect is a method used to detect Getters by validating if // a source string is detected to be of a known pattern, // and to transform it to a known pattern when necessary. // -// This is the logic of the getters chain of detection. -// When a getter detects or not a valid source string, it will -// call the next getter that will then use this method to do the same. -func Detect(src string, pwd string, g Getter) (string, []Getter, error) { - gList := []Getter{g} - getForce, getSrc := getForcedGetter(src) - isForcedGetter := getForce != "" && g.ValidScheme(getForce) - - // Separate out the subdir if there is one, we don't pass that to detect - getSrc, subDir := SourceDirSubdir(getSrc) +// The result is a list of possible Getters to download an artifact. +func Detect(src, pwd string, gs []Getter) (string, []Getter, error) { + resultSrc := src + var validGetters []Getter + for _, getter := range gs { + gList := []Getter{getter} + getForce, getSrc := getForcedGetter(resultSrc) + isForcedGetter := getForce != "" && getter.ValidScheme(getForce) + + // Separate out the subdir if there is one, we don't pass that to detect + getSrc, subDir := SourceDirSubdir(getSrc) + + u, err := url.Parse(getSrc) + if err == nil && u.Scheme != "" { + if !isForcedGetter && !getter.ValidScheme(u.Scheme) { + // Not forced getter and not valid scheme + continue + } + if !isForcedGetter && getter.ValidScheme(u.Scheme) { + // Not forced but a valid scheme for current getter + validGetters = append(validGetters, getter) + continue + } + if isForcedGetter { + // With a forced getter and another scheme, we want to try only the force getter + return getSrc, gList, nil + } + } - u, err := url.Parse(getSrc) - if err == nil && u.Scheme != "" { - if !isForcedGetter && !g.ValidScheme(u.Scheme) { - // Not forced getter and not valid scheme - // Keep going through the chain to find valid getters for this scheme - return tryNextGetter(src, pwd, g) + result, ok, err := getter.Detect(getSrc, pwd) + if err != nil { + return "", nil, err } - if !isForcedGetter && g.ValidScheme(u.Scheme) { - // Not forced but a valid scheme for current getter - // Keep going through the chain to find other valid getters for this scheme - _, gs, err := tryNextGetter(src, pwd, g) - if gs != nil && err == nil { - // append this getter to the list of valid getters - return getSrc, append(gList, gs...), nil + if !ok { + if isForcedGetter { + // Adds the forced getter to the list but keep transforming the source string + _, resultSrc = getForcedGetter(resultSrc) + validGetters = append(validGetters, getter) } - return getSrc, gList, nil - } - if isForcedGetter { - // With a forced getter and another scheme, we want to try only the force getter - return getSrc, gList, nil + continue } - } - result, ok, err := g.DetectGetter(getSrc, pwd) - if err != nil { - return "", nil, err - } - if !ok { - // Try next of the chain - r, gs, err := tryNextGetter(src, pwd, g) - if isForcedGetter && err == nil { - // Remove any forced getter from the path - _, r = getForcedGetter(r) - return r, gList, nil - } - return r, gs, err - } + result, detectSubdir := SourceDirSubdir(result) - result, detectSubdir := SourceDirSubdir(result) + // If we have a subdir from the detection, then prepend it to our + // requested subdir. + if detectSubdir != "" { + if subDir != "" { + subDir = filepath.Join(detectSubdir, subDir) + } else { + subDir = detectSubdir + } + } - // If we have a subdir from the detection, then prepend it to our - // requested subdir. - if detectSubdir != "" { if subDir != "" { - subDir = filepath.Join(detectSubdir, subDir) - } else { - subDir = detectSubdir - } - } + u, err := url.Parse(result) + if err != nil { + return "", nil, fmt.Errorf("Error parsing URL: %s", err) + } + u.Path += "//" + subDir - if subDir != "" { - u, err := url.Parse(result) - if err != nil { - return "", gList, fmt.Errorf("Error parsing URL: %s", err) - } - u.Path += "//" + subDir + // a subdir may contain wildcards, but in order to support them we + // have to ensure the path isn't escaped. + u.RawPath = u.Path - // a subdir may contain wildcards, but in order to support them we - // have to ensure the path isn't escaped. - u.RawPath = u.Path + result = u.String() + } - result = u.String() - } + resultSrc = result + if getForce != "" && !isForcedGetter { + // If there's a forced getter and it's not the current one + // We don't append current getter to the list and try next getter + resultSrc = fmt.Sprintf("%s::%s", getForce, result) + continue + } - if getForce != "" && !isForcedGetter { - // If there's a forced getter and it's not the current one - // We don't append current getter to the list and try next getter - result = fmt.Sprintf("%s::%s", getForce, result) - _, gs, err := tryNextGetter(result, pwd, g) - return result, gs, err + // this is valid by getter detection + validGetters = append(validGetters, getter) } - // this is valid by getter detection - r, gs, err := tryNextGetter(result, pwd, g) - if err != nil && gs != nil { - return r, append(gList, gs...), nil + if len(validGetters) == 0 { + return "", nil, fmt.Errorf("couldn't find any available getter") } - return result, gList, nil -} -func tryNextGetter(src string, pwd string, g Getter) (string, []Getter, error) { - if g.Next() != nil { - return g.Next().Detect(src, pwd) - } - return src, nil, nil + _, getSrc := getForcedGetter(resultSrc) + return getSrc, validGetters, nil } diff --git a/detector_test.go b/detector_test.go index b04765651..326602532 100644 --- a/detector_test.go +++ b/detector_test.go @@ -92,8 +92,7 @@ func TestDetect(t *testing.T) { httpGetter, } t.Run(fmt.Sprintf("%d %s", i, tc.Input), func(t *testing.T) { - detector := NewGetterDetector(Getters) - output, err := detector.Detect(tc.Input, tc.Pwd) + output, _, err := Detect(tc.Input, tc.Pwd, Getters) if err != nil != tc.Err { t.Fatalf("%d: bad err: %s", i, err) } diff --git a/get.go b/get.go index 8a6b9eae7..cca50a5f3 100644 --- a/get.go +++ b/get.go @@ -45,13 +45,7 @@ type Getter interface { // Detect will detect whether the string matches a known pattern to // turn it into a proper URL. - DetectGetter(string, string) (string, bool, error) - - Detect(string, string) (string, []Getter, error) - - SetNext(Getter) - - Next() Getter + Detect(string, string) (string, bool, error) ValidScheme(string) bool } diff --git a/get_file.go b/get_file.go index 05db0966a..41176df26 100644 --- a/get_file.go +++ b/get_file.go @@ -151,7 +151,7 @@ func (g *FileGetter) GetFile(ctx context.Context, req *Request) error { return err } -func (g *FileGetter) DetectGetter(src string, pwd string) (string, bool, error) { +func (g *FileGetter) Detect(src string, pwd string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -210,15 +210,3 @@ func fmtFileURL(path string) string { } return path } - -func (g *FileGetter) Detect(src, pwd string) (string, []Getter, error) { - return Detect(src, pwd, g) -} - -func (g *FileGetter) Next() Getter { - return g.next -} - -func (g *FileGetter) SetNext(next Getter) { - g.next = next -} diff --git a/get_gcs.go b/get_gcs.go index e84737ab5..6449e456c 100644 --- a/get_gcs.go +++ b/get_gcs.go @@ -166,7 +166,7 @@ func (g *GCSGetter) parseURL(u *url.URL) (bucket, path string, err error) { return } -func (g *GCSGetter) DetectGetter(src, _ string) (string, bool, error) { +func (g *GCSGetter) Detect(src, _ string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -207,15 +207,3 @@ func (g *GCSGetter) detectHTTP(src string) (string, bool, error) { func (g *GCSGetter) ValidScheme(scheme string) bool { return scheme == "gcs" } - -func (g *GCSGetter) Detect(src, pwd string) (string, []Getter, error) { - return Detect(src, pwd, g) -} - -func (g *GCSGetter) Next() Getter { - return g.next -} - -func (g *GCSGetter) SetNext(next Getter) { - g.next = next -} diff --git a/get_git.go b/get_git.go index a2d7caea8..10b958966 100644 --- a/get_git.go +++ b/get_git.go @@ -317,7 +317,7 @@ func checkGitVersion(min string) error { return nil } -func (g *GitGetter) DetectGetter(src, _ string) (string, bool, error) { +func (g *GitGetter) Detect(src, _ string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -425,15 +425,3 @@ func detectGitHub(src string) (string, bool, error) { func (g *GitGetter) ValidScheme(scheme string) bool { return scheme == "git" || scheme == "ssh" } - -func (g *GitGetter) Detect(src, pwd string) (string, []Getter, error) { - return Detect(src, pwd, g) -} - -func (g *GitGetter) Next() Getter { - return g.next -} - -func (g *GitGetter) SetNext(next Getter) { - g.next = next -} diff --git a/get_hg.go b/get_hg.go index 3bbab53ec..7064782e8 100644 --- a/get_hg.go +++ b/get_hg.go @@ -125,7 +125,7 @@ func (g *HgGetter) update(ctx context.Context, dst string, u *url.URL, rev strin return getRunCommand(cmd) } -func (g *HgGetter) DetectGetter(src, _ string) (string, bool, error) { +func (g *HgGetter) Detect(src, _ string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -161,15 +161,3 @@ func fixWindowsDrivePath(u *url.URL) bool { return runtime.GOOS == "windows" && u.Scheme == "file" && len(u.Path) > 1 && u.Path[0] != '/' && u.Path[1] == ':' } - -func (g *HgGetter) Detect(src, pwd string) (string, []Getter, error) { - return Detect(src, pwd, g) -} - -func (g *HgGetter) Next() Getter { - return g.next -} - -func (g *HgGetter) SetNext(next Getter) { - g.next = next -} diff --git a/get_http.go b/get_http.go index 2f056b80f..c3ca62e8f 100644 --- a/get_http.go +++ b/get_http.go @@ -321,7 +321,7 @@ func charsetReader(charset string, input io.Reader) (io.Reader, error) { } } -func (g *HttpGetter) DetectGetter(src, _ string) (string, bool, error) { +func (g *HttpGetter) Detect(src, _ string) (string, bool, error) { u, err := url.Parse(src) if err == nil && u.Scheme != "" && (u.Scheme == "http" || u.Scheme == "https") { // Valid URL @@ -333,15 +333,3 @@ func (g *HttpGetter) DetectGetter(src, _ string) (string, bool, error) { func (g *HttpGetter) ValidScheme(scheme string) bool { return scheme == "http" || scheme == "https" } - -func (g *HttpGetter) Detect(src, pwd string) (string, []Getter, error) { - return Detect(src, pwd, g) -} - -func (g *HttpGetter) Next() Getter { - return g.next -} - -func (g *HttpGetter) SetNext(next Getter) { - g.next = next -} diff --git a/get_mock.go b/get_mock.go index 7714152a4..be85b23e2 100644 --- a/get_mock.go +++ b/get_mock.go @@ -52,11 +52,11 @@ func (g *MockGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return ModeFile, nil } -func (g *MockGetter) DetectGetter(src, pwd string) (string, bool, error) { +func (g *MockGetter) Detect(src, pwd string) (string, bool, error) { if g.Proxy != nil { - return g.Proxy.DetectGetter(src, pwd) + return g.Proxy.Detect(src, pwd) } - return "", true, nil + return src, true, nil } func (g *MockGetter) ValidScheme(scheme string) bool { @@ -65,21 +65,3 @@ func (g *MockGetter) ValidScheme(scheme string) bool { } return true } - -func (g *MockGetter) Detect(src, pwd string) (string, []Getter, error) { - if g.Proxy != nil { - detect, _, err := g.Proxy.Detect(src, pwd) - return detect, []Getter{g}, err - } - return src, []Getter{g}, nil -} - -func (g *MockGetter) Next() Getter { - if g.Proxy != nil { - return g.Proxy.Next() - } - return nil -} - -func (g *MockGetter) SetNext(next Getter) { -} diff --git a/get_s3.go b/get_s3.go index 0feb7064c..8794836bf 100644 --- a/get_s3.go +++ b/get_s3.go @@ -272,7 +272,7 @@ func (g *S3Getter) parseUrl(u *url.URL) (region, bucket, path, version string, c return } -func (g *S3Getter) DetectGetter(src, _ string) (string, bool, error) { +func (g *S3Getter) Detect(src, _ string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -331,15 +331,3 @@ func (g *S3Getter) detectVhostStyle(region, bucket string, parts []string) (stri func (g *S3Getter) ValidScheme(scheme string) bool { return scheme == "s3" } - -func (g *S3Getter) Detect(src, pwd string) (string, []Getter, error) { - return Detect(src, pwd, g) -} - -func (g *S3Getter) Next() Getter { - return g.next -} - -func (g *S3Getter) SetNext(next Getter) { - g.next = next -} diff --git a/get_smb_mount.go b/get_smb_mount.go index 79d816f09..b241f057c 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -202,7 +202,7 @@ func (g *SmbMountGetter) findShare(u *url.URL) string { return "." } -func (g *SmbMountGetter) DetectGetter(src, pwd string) (string, bool, error) { +func (g *SmbMountGetter) Detect(src, pwd string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -219,15 +219,3 @@ func (g *SmbMountGetter) DetectGetter(src, pwd string) (string, bool, error) { func (g *SmbMountGetter) ValidScheme(scheme string) bool { return scheme == "smb" } - -func (g *SmbMountGetter) Detect(src, pwd string) (string, []Getter, error) { - return Detect(src, pwd, g) -} - -func (g *SmbMountGetter) Next() Getter { - return g.next -} - -func (g *SmbMountGetter) SetNext(next Getter) { - g.next = next -} diff --git a/get_smbclient.go b/get_smbclient.go index 0752a851f..fe19954fc 100644 --- a/get_smbclient.go +++ b/get_smbclient.go @@ -283,7 +283,7 @@ func (g *SmbClientGetter) runSmbClientCommand(dst string, args []string) (string return buf.String(), fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) } -func (g *SmbClientGetter) DetectGetter(src, pwd string) (string, bool, error) { +func (g *SmbClientGetter) Detect(src, pwd string) (string, bool, error) { if len(src) == 0 { return "", false, nil } @@ -301,18 +301,6 @@ func (g *SmbClientGetter) ValidScheme(scheme string) bool { return scheme == "smb" } -func (g *SmbClientGetter) Detect(src, pwd string) (string, []Getter, error) { - return Detect(src, pwd, g) -} - -func (g *SmbClientGetter) Next() Getter { - return g.next -} - -func (g *SmbClientGetter) SetNext(next Getter) { - g.next = next -} - type smbPathError struct { Path string } From cfb908eb4b4ef44096cce382259027291494c22c Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 7 May 2020 14:21:25 +0200 Subject: [PATCH 084/109] loop over getters and detect for each --- client.go | 168 +++++++++++++++++++-------------------- detect_bitbucket_test.go | 6 +- detect_file_test.go | 12 ++- detect_gcs_test.go | 6 +- detect_git_test.go | 6 +- detect_github_test.go | 6 +- detect_s3_test.go | 6 +- detector.go | 135 +++++++++++++++---------------- detector_test.go | 33 ++++---- get.go | 2 +- get_file.go | 5 +- get_gcs.go | 3 +- get_git.go | 7 +- get_hg.go | 3 +- get_http.go | 3 +- get_mock.go | 6 +- get_s3.go | 3 +- get_smb_mount.go | 3 +- get_smbclient.go | 3 +- 19 files changed, 226 insertions(+), 190 deletions(-) diff --git a/client.go b/client.go index 2c1de87dc..387d535f6 100644 --- a/client.go +++ b/client.go @@ -46,103 +46,103 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { req.Mode = ModeAny } - // Run over the getters to get a list of possible Getters. - // Some urls can be supported by more than one Getter and we want to make sure - // the best getter option will try to download first. - // The Getters slice must be in priority order. - src, getters, err := Detect(req.Src, req.Pwd, c.Getters) - if err != nil { - return nil, err - } - req.Src = src - - // If there is a subdir component, then we download the root separately - // and then copy over the proper subdir. - var realDst, subDir string - req.Src, subDir = SourceDirSubdir(req.Src) - if subDir != "" { - td, tdcloser, err := safetemp.Dir("", "getter") + var modeErr *multierror.Error + var getFileErr *multierror.Error + var getErr *multierror.Error + for _, g := range c.Getters { + src, ok, err := Detect(req, g) if err != nil { return nil, err } - defer tdcloser.Close() - - realDst = req.Dst - req.Dst = td - } + if !ok { + continue + } + req.Src = src - req.u, err = urlhelper.Parse(req.Src) - if err != nil { - return nil, err - } + // If there is a subdir component, then we download the root separately + // and then copy over the proper subdir. + var realDst, subDir string + req.Src, subDir = SourceDirSubdir(req.Src) + if subDir != "" { + td, tdcloser, err := safetemp.Dir("", "getter") + if err != nil { + return nil, err + } + defer tdcloser.Close() - // We have magic query parameters that we use to signal different features - q := req.u.Query() + realDst = req.Dst + req.Dst = td + } - // Determine if we have an archive type - archiveV := q.Get("archive") - if archiveV != "" { - // Delete the paramter since it is a magic parameter we don't - // want to pass on to the Getter - q.Del("archive") - req.u.RawQuery = q.Encode() + req.u, err = urlhelper.Parse(req.Src) + if err != nil { + return nil, err + } - // If we can parse the value as a bool and it is false, then - // set the archive to "-" which should never map to a decompressor - if b, err := strconv.ParseBool(archiveV); err == nil && !b { - archiveV = "-" + // We have magic query parameters that we use to signal different features + q := req.u.Query() + + // Determine if we have an archive type + archiveV := q.Get("archive") + if archiveV != "" { + // Delete the paramter since it is a magic parameter we don't + // want to pass on to the Getter + q.Del("archive") + req.u.RawQuery = q.Encode() + + // If we can parse the value as a bool and it is false, then + // set the archive to "-" which should never map to a decompressor + if b, err := strconv.ParseBool(archiveV); err == nil && !b { + archiveV = "-" + } + } else { + // We don't appear to... but is it part of the filename? + matchingLen := 0 + for k := range c.Decompressors { + if strings.HasSuffix(req.u.Path, "."+k) && len(k) > matchingLen { + archiveV = k + matchingLen = len(k) + } + } } - } else { - // We don't appear to... but is it part of the filename? - matchingLen := 0 - for k := range c.Decompressors { - if strings.HasSuffix(req.u.Path, "."+k) && len(k) > matchingLen { - archiveV = k - matchingLen = len(k) + + // If we have a decompressor, then we need to change the destination + // to download to a temporary path. We unarchive this into the final, + // real path. + var decompressDst string + var decompressDir bool + decompressor := c.Decompressors[archiveV] + if decompressor != nil { + // Create a temporary directory to store our archive. We delete + // this at the end of everything. + td, err := ioutil.TempDir("", "getter") + if err != nil { + return nil, fmt.Errorf( + "Error creating temporary directory for archive: %s", err) } + defer os.RemoveAll(td) + + // Swap the download directory to be our temporary path and + // store the old values. + decompressDst = req.Dst + decompressDir = req.Mode != ModeFile + req.Dst = filepath.Join(td, "archive") + req.Mode = ModeFile } - } - // If we have a decompressor, then we need to change the destination - // to download to a temporary path. We unarchive this into the final, - // real path. - var decompressDst string - var decompressDir bool - decompressor := c.Decompressors[archiveV] - if decompressor != nil { - // Create a temporary directory to store our archive. We delete - // this at the end of everything. - td, err := ioutil.TempDir("", "getter") + // Determine checksum if we have one + checksum, err := c.extractChecksum(ctx, req.u) if err != nil { - return nil, fmt.Errorf( - "Error creating temporary directory for archive: %s", err) + return nil, fmt.Errorf("invalid checksum: %s", err) } - defer os.RemoveAll(td) - - // Swap the download directory to be our temporary path and - // store the old values. - decompressDst = req.Dst - decompressDir = req.Mode != ModeFile - req.Dst = filepath.Join(td, "archive") - req.Mode = ModeFile - } - // Determine checksum if we have one - checksum, err := c.extractChecksum(ctx, req.u) - if err != nil { - return nil, fmt.Errorf("invalid checksum: %s", err) - } + // Delete the query parameter if we have it. + q.Del("checksum") + req.u.RawQuery = q.Encode() - // Delete the query parameter if we have it. - q.Del("checksum") - req.u.RawQuery = q.Encode() + // From now one, each possible getter will + // try to run the rest of the logic - // From now one, each possible getter will - // try to run the rest of the logic - var modeErr *multierror.Error - var getFileErr *multierror.Error - var getErr *multierror.Error - for _, g := range getters { if req.Mode == ModeAny { // Ask the getter which client mode to use req.Mode, err = g.Mode(ctx, req.u) @@ -262,13 +262,13 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // If there's an getErr or getFileErr, we can ignore any modeErr because is // going to be a getter that didn't get to far on downloading the artifact if getErr != nil { - err = fmt.Errorf("error downloading '%s': %s", req.Src, getErr) + return nil, fmt.Errorf("error downloading '%s': %s", req.Src, getErr) } if getFileErr != nil { - err = fmt.Errorf("error downloading '%s': %s", req.Src, getFileErr) + return nil, fmt.Errorf("error downloading '%s': %s", req.Src, getFileErr) } if modeErr != nil { - err = fmt.Errorf("error downloading '%s': %s", req.Src, modeErr) + return nil, fmt.Errorf("error downloading '%s': %s", req.Src, modeErr) } return nil, fmt.Errorf("error downloading '%s'", req.Src) } diff --git a/detect_bitbucket_test.go b/detect_bitbucket_test.go index e9c4c256f..9296e53c9 100644 --- a/detect_bitbucket_test.go +++ b/detect_bitbucket_test.go @@ -45,7 +45,11 @@ func TestBitBucketDetector(t *testing.T) { for i := 0; i < 3; i++ { var output string var ok bool - output, ok, err = tc.g.Detect(tc.Input, pwd) + req := &Request{ + Src: tc.Input, + Pwd: pwd, + } + output, ok, err = Detect(req, tc.g) if err != nil { if strings.Contains(err.Error(), "invalid character") { continue diff --git a/detect_file_test.go b/detect_file_test.go index dbe794d4c..2d4f904db 100644 --- a/detect_file_test.go +++ b/detect_file_test.go @@ -55,7 +55,11 @@ func TestFileDetector(t *testing.T) { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { pwd := tc.pwd - out, ok, err := f.Detect(tc.in, pwd) + req := &Request{ + Src: tc.in, + Pwd: pwd, + } + out, ok, err := Detect(req, f) if err != nil { t.Fatalf("err: %s", err) } @@ -99,7 +103,11 @@ func TestFileDetector_noPwd(t *testing.T) { f := new(FileGetter) for i, tc := range noPwdFileTests { - out, ok, err := f.Detect(tc.in, tc.pwd) + req := &Request{ + Src: tc.in, + Pwd: tc.pwd, + } + out, ok, err := Detect(req, f) if err != nil != tc.err { t.Fatalf("%d: err: %s", i, err) } diff --git a/detect_gcs_test.go b/detect_gcs_test.go index 8f78e8a2a..5f2c00002 100644 --- a/detect_gcs_test.go +++ b/detect_gcs_test.go @@ -26,7 +26,11 @@ func TestGCSDetector(t *testing.T) { pwd := "/pwd" f := new(GCSGetter) for i, tc := range cases { - output, ok, err := f.Detect(tc.Input, pwd) + req := &Request{ + Src: tc.Input, + Pwd: pwd, + } + output, ok, err := Detect(req, f) if err != nil { t.Fatalf("err: %s", err) } diff --git a/detect_git_test.go b/detect_git_test.go index a62ea4f83..5a882bae7 100644 --- a/detect_git_test.go +++ b/detect_git_test.go @@ -50,7 +50,11 @@ func TestGitDetector(t *testing.T) { pwd := "/pwd" for _, tc := range cases { t.Run(tc.Input, func(t *testing.T) { - output, _, err := Detect(tc.Input, pwd, []Getter{new(GitGetter)}) + req := &Request{ + Src: tc.Input, + Pwd: pwd, + } + output, _, err := Detect(req, new(GitGetter)) if err != nil { t.Fatalf("unexpected error: %s", err) } diff --git a/detect_github_test.go b/detect_github_test.go index 427e175ad..c1a45d547 100644 --- a/detect_github_test.go +++ b/detect_github_test.go @@ -29,7 +29,11 @@ func TestGitHubDetector(t *testing.T) { pwd := "/pwd" f := new(GitGetter) for i, tc := range cases { - output, ok, err := f.Detect(tc.Input, pwd) + req := &Request{ + Src: tc.Input, + Pwd: pwd, + } + output, ok, err := Detect(req, f) if err != nil { t.Fatalf("err: %s", err) } diff --git a/detect_s3_test.go b/detect_s3_test.go index 015efbdcb..adec07812 100644 --- a/detect_s3_test.go +++ b/detect_s3_test.go @@ -69,7 +69,11 @@ func TestS3Detector(t *testing.T) { pwd := "/pwd" f := new(S3Getter) for i, tc := range cases { - output, ok, err := f.Detect(tc.Input, pwd) + req := &Request{ + Src: tc.Input, + Pwd: pwd, + } + output, ok, err := Detect(req, f) if err != nil { t.Fatalf("err: %s", err) } diff --git a/detector.go b/detector.go index 74c0f71e6..9f2476384 100644 --- a/detector.go +++ b/detector.go @@ -6,94 +6,85 @@ import ( "path/filepath" ) -// Detect is a method used to detect Getters by validating if -// a source string is detected to be of a known pattern, +// Detect is a method used to detect if a Getter is a candidate for downloading and artifact +// by validating if a source string is detected to be of a known pattern, // and to transform it to a known pattern when necessary. -// -// The result is a list of possible Getters to download an artifact. -func Detect(src, pwd string, gs []Getter) (string, []Getter, error) { - resultSrc := src - var validGetters []Getter - for _, getter := range gs { - gList := []Getter{getter} - getForce, getSrc := getForcedGetter(resultSrc) - isForcedGetter := getForce != "" && getter.ValidScheme(getForce) +func Detect(req *Request, getter Getter) (string, bool, error) { + originalSrc := req.Src - // Separate out the subdir if there is one, we don't pass that to detect - getSrc, subDir := SourceDirSubdir(getSrc) + getForce, getSrc := getForcedGetter(req.Src) - u, err := url.Parse(getSrc) - if err == nil && u.Scheme != "" { - if !isForcedGetter && !getter.ValidScheme(u.Scheme) { - // Not forced getter and not valid scheme - continue - } - if !isForcedGetter && getter.ValidScheme(u.Scheme) { - // Not forced but a valid scheme for current getter - validGetters = append(validGetters, getter) - continue - } - if isForcedGetter { - // With a forced getter and another scheme, we want to try only the force getter - return getSrc, gList, nil - } + if getForce != "" { + // There's a getter being forced + if !getter.ValidScheme(getForce) { + // Current getter is not the forced one + // Don't use it to try to download the artifact + return "", false, nil } + } - result, ok, err := getter.Detect(getSrc, pwd) - if err != nil { - return "", nil, err - } - if !ok { - if isForcedGetter { - // Adds the forced getter to the list but keep transforming the source string - _, resultSrc = getForcedGetter(resultSrc) - validGetters = append(validGetters, getter) - } - continue - } + isForcedGetter := getForce != "" && getter.ValidScheme(getForce) - result, detectSubdir := SourceDirSubdir(result) + // Separate out the subdir if there is one, we don't pass that to detect + getSrc, subDir := SourceDirSubdir(getSrc) - // If we have a subdir from the detection, then prepend it to our - // requested subdir. - if detectSubdir != "" { - if subDir != "" { - subDir = filepath.Join(detectSubdir, subDir) - } else { - subDir = detectSubdir - } + u, err := url.Parse(getSrc) + if err == nil && u.Scheme != "" { + if isForcedGetter { + // Is the forced getter and source is a valid url + return getSrc, true, nil } - - if subDir != "" { - u, err := url.Parse(result) - if err != nil { - return "", nil, fmt.Errorf("Error parsing URL: %s", err) + if getter.ValidScheme(u.Scheme) { + return getSrc, true, nil + } else { + // Valid url with a scheme that is not valid for current getter + return "", false, nil + } + } + req.Src = getSrc + result, ok, err := getter.Detect(req) + if err != nil { + return "", true, err + } + if !ok { + if isForcedGetter { + // Is the forced getter then should be used to download the artifact + if req.Pwd != "" && !filepath.IsAbs(getSrc) { + // Make sure to add pwd to relative paths + getSrc = filepath.Join(req.Pwd, getSrc) } - u.Path += "//" + subDir + return getSrc, true, nil + } + // Write back the original source + req.Src = originalSrc + return "", ok, nil + } - // a subdir may contain wildcards, but in order to support them we - // have to ensure the path isn't escaped. - u.RawPath = u.Path + result, detectSubdir := SourceDirSubdir(result) - result = u.String() + // If we have a subdir from the detection, then prepend it to our + // requested subdir. + if detectSubdir != "" { + if subDir != "" { + subDir = filepath.Join(detectSubdir, subDir) + } else { + subDir = detectSubdir } + } - resultSrc = result - if getForce != "" && !isForcedGetter { - // If there's a forced getter and it's not the current one - // We don't append current getter to the list and try next getter - resultSrc = fmt.Sprintf("%s::%s", getForce, result) - continue + if subDir != "" { + u, err := url.Parse(result) + if err != nil { + return "", true, fmt.Errorf("Error parsing URL: %s", err) } + u.Path += "//" + subDir - // this is valid by getter detection - validGetters = append(validGetters, getter) - } + // a subdir may contain wildcards, but in order to support them we + // have to ensure the path isn't escaped. + u.RawPath = u.Path - if len(validGetters) == 0 { - return "", nil, fmt.Errorf("couldn't find any available getter") + result = u.String() } - _, getSrc := getForcedGetter(resultSrc) - return getSrc, validGetters, nil + return result, true, nil } diff --git a/detector_test.go b/detector_test.go index 326602532..b8679b393 100644 --- a/detector_test.go +++ b/detector_test.go @@ -11,50 +11,58 @@ func TestDetect(t *testing.T) { Pwd string Output string Err bool + getter Getter }{ - {"./foo", "/foo", "/foo/foo", false}, - {"git::./foo", "/foo", "/foo/foo", false}, + {"./foo", "/foo", "/foo/foo", false, new(FileGetter)}, + {"git::./foo", "/foo", "/foo/foo", false, new(GitGetter)}, { "git::github.com/hashicorp/foo", "", "https://github.com/hashicorp/foo.git", false, + new(GitGetter), }, { "./foo//bar", "/foo", "/foo/foo//bar", false, + new(FileGetter), }, { "git::github.com/hashicorp/foo//bar", "", "https://github.com/hashicorp/foo.git//bar", false, + new(GitGetter), }, { "git::https://github.com/hashicorp/consul.git", "", "https://github.com/hashicorp/consul.git", false, + new(GitGetter), }, { "git::https://person@someothergit.com/foo/bar", // this "", "https://person@someothergit.com/foo/bar", false, + new(GitGetter), }, { "git::https://person@someothergit.com/foo/bar", // this "/bar", "https://person@someothergit.com/foo/bar", false, + new(GitGetter), }, { "./foo/archive//*", "/bar", "/bar/foo/archive//*", false, + new(FileGetter), }, // https://github.com/hashicorp/go-getter/pull/124 @@ -63,36 +71,31 @@ func TestDetect(t *testing.T) { "", "ssh://git@my.custom.git/dir1/dir2", false, + new(GitGetter), }, { "git::git@my.custom.git:dir1/dir2", "/foo", "ssh://git@my.custom.git/dir1/dir2", false, + new(GitGetter), }, { "git::git@my.custom.git:dir1/dir2", "", "ssh://git@my.custom.git/dir1/dir2", false, + new(GitGetter), }, } for i, tc := range cases { - httpGetter := &HttpGetter{ - Netrc: true, - } - Getters = []Getter{ - new(GitGetter), - new(HgGetter), - new(S3Getter), - new(GCSGetter), - new(FileGetter), - new(SmbClientGetter), - httpGetter, - } t.Run(fmt.Sprintf("%d %s", i, tc.Input), func(t *testing.T) { - output, _, err := Detect(tc.Input, tc.Pwd, Getters) + req := &Request{ + Src: tc.Input, + Pwd: tc.Pwd, + } + output, _, err := Detect(req, tc.getter) if err != nil != tc.Err { t.Fatalf("%d: bad err: %s", i, err) } diff --git a/get.go b/get.go index cca50a5f3..015730e89 100644 --- a/get.go +++ b/get.go @@ -45,7 +45,7 @@ type Getter interface { // Detect will detect whether the string matches a known pattern to // turn it into a proper URL. - Detect(string, string) (string, bool, error) + Detect(*Request) (string, bool, error) ValidScheme(string) bool } diff --git a/get_file.go b/get_file.go index 41176df26..05540e748 100644 --- a/get_file.go +++ b/get_file.go @@ -151,7 +151,10 @@ func (g *FileGetter) GetFile(ctx context.Context, req *Request) error { return err } -func (g *FileGetter) Detect(src string, pwd string) (string, bool, error) { +func (g *FileGetter) Detect(req *Request) (string, bool, error) { + var src, pwd string + src = req.Src + pwd = req.Pwd if len(src) == 0 { return "", false, nil } diff --git a/get_gcs.go b/get_gcs.go index 6449e456c..f72808df6 100644 --- a/get_gcs.go +++ b/get_gcs.go @@ -166,7 +166,8 @@ func (g *GCSGetter) parseURL(u *url.URL) (bucket, path string, err error) { return } -func (g *GCSGetter) Detect(src, _ string) (string, bool, error) { +func (g *GCSGetter) Detect(req *Request) (string, bool, error) { + src := req.Src if len(src) == 0 { return "", false, nil } diff --git a/get_git.go b/get_git.go index 10b958966..8107a1290 100644 --- a/get_git.go +++ b/get_git.go @@ -317,7 +317,8 @@ func checkGitVersion(min string) error { return nil } -func (g *GitGetter) Detect(src, _ string) (string, bool, error) { +func (g *GitGetter) Detect(req *Request) (string, bool, error) { + src := req.Src if len(src) == 0 { return "", false, nil } @@ -358,6 +359,10 @@ func (g *GitGetter) Detect(src, _ string) (string, bool, error) { return u.String(), true, nil } + if _, err = url.Parse(req.Src); err != nil { + return "", true, nil + } + return "", false, nil } diff --git a/get_hg.go b/get_hg.go index 7064782e8..a58341ae5 100644 --- a/get_hg.go +++ b/get_hg.go @@ -125,7 +125,8 @@ func (g *HgGetter) update(ctx context.Context, dst string, u *url.URL, rev strin return getRunCommand(cmd) } -func (g *HgGetter) Detect(src, _ string) (string, bool, error) { +func (g *HgGetter) Detect(req *Request) (string, bool, error) { + src := req.Src if len(src) == 0 { return "", false, nil } diff --git a/get_http.go b/get_http.go index c3ca62e8f..774f03f31 100644 --- a/get_http.go +++ b/get_http.go @@ -321,7 +321,8 @@ func charsetReader(charset string, input io.Reader) (io.Reader, error) { } } -func (g *HttpGetter) Detect(src, _ string) (string, bool, error) { +func (g *HttpGetter) Detect(req *Request) (string, bool, error) { + src := req.Src u, err := url.Parse(src) if err == nil && u.Scheme != "" && (u.Scheme == "http" || u.Scheme == "https") { // Valid URL diff --git a/get_mock.go b/get_mock.go index be85b23e2..80312e9ed 100644 --- a/get_mock.go +++ b/get_mock.go @@ -52,11 +52,11 @@ func (g *MockGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return ModeFile, nil } -func (g *MockGetter) Detect(src, pwd string) (string, bool, error) { +func (g *MockGetter) Detect(req *Request) (string, bool, error) { if g.Proxy != nil { - return g.Proxy.Detect(src, pwd) + return g.Proxy.Detect(req) } - return src, true, nil + return req.Src, true, nil } func (g *MockGetter) ValidScheme(scheme string) bool { diff --git a/get_s3.go b/get_s3.go index 8794836bf..423c36f91 100644 --- a/get_s3.go +++ b/get_s3.go @@ -272,7 +272,8 @@ func (g *S3Getter) parseUrl(u *url.URL) (region, bucket, path, version string, c return } -func (g *S3Getter) Detect(src, _ string) (string, bool, error) { +func (g *S3Getter) Detect(req *Request) (string, bool, error) { + src := req.Src if len(src) == 0 { return "", false, nil } diff --git a/get_smb_mount.go b/get_smb_mount.go index b241f057c..293d666de 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -202,7 +202,8 @@ func (g *SmbMountGetter) findShare(u *url.URL) string { return "." } -func (g *SmbMountGetter) Detect(src, pwd string) (string, bool, error) { +func (g *SmbMountGetter) Detect(req *Request) (string, bool, error) { + src := req.Src if len(src) == 0 { return "", false, nil } diff --git a/get_smbclient.go b/get_smbclient.go index fe19954fc..0b322ae81 100644 --- a/get_smbclient.go +++ b/get_smbclient.go @@ -283,7 +283,8 @@ func (g *SmbClientGetter) runSmbClientCommand(dst string, args []string) (string return buf.String(), fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) } -func (g *SmbClientGetter) Detect(src, pwd string) (string, bool, error) { +func (g *SmbClientGetter) Detect(req *Request) (string, bool, error) { + src := req.Src if len(src) == 0 { return "", false, nil } From e13d3cad6922fdac9c22371ebc24570972f6516f Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 7 May 2020 15:01:19 +0200 Subject: [PATCH 085/109] Remove ValidScheme api and move some detection logic to Getters --- detector.go | 33 +-------------------------------- get.go | 2 -- get_file.go | 26 +++++++++++++++++++++----- get_gcs.go | 35 ++++++++++++++++++++++++++++++----- get_git.go | 35 ++++++++++++++++++++++++++++++----- get_hg.go | 35 ++++++++++++++++++++++++++++++----- get_http.go | 39 ++++++++++++++++++++++++++++++++++----- get_mock.go | 7 ------- get_s3.go | 35 ++++++++++++++++++++++++++++++----- get_smb_mount.go | 33 +++++++++++++++++++++++++++++---- get_smbclient.go | 33 +++++++++++++++++++++++++++++---- request.go | 4 ++++ 12 files changed, 238 insertions(+), 79 deletions(-) diff --git a/detector.go b/detector.go index 9f2476384..7c08f4403 100644 --- a/detector.go +++ b/detector.go @@ -13,48 +13,17 @@ func Detect(req *Request, getter Getter) (string, bool, error) { originalSrc := req.Src getForce, getSrc := getForcedGetter(req.Src) - - if getForce != "" { - // There's a getter being forced - if !getter.ValidScheme(getForce) { - // Current getter is not the forced one - // Don't use it to try to download the artifact - return "", false, nil - } - } - - isForcedGetter := getForce != "" && getter.ValidScheme(getForce) + req.forced = getForce // Separate out the subdir if there is one, we don't pass that to detect getSrc, subDir := SourceDirSubdir(getSrc) - u, err := url.Parse(getSrc) - if err == nil && u.Scheme != "" { - if isForcedGetter { - // Is the forced getter and source is a valid url - return getSrc, true, nil - } - if getter.ValidScheme(u.Scheme) { - return getSrc, true, nil - } else { - // Valid url with a scheme that is not valid for current getter - return "", false, nil - } - } req.Src = getSrc result, ok, err := getter.Detect(req) if err != nil { return "", true, err } if !ok { - if isForcedGetter { - // Is the forced getter then should be used to download the artifact - if req.Pwd != "" && !filepath.IsAbs(getSrc) { - // Make sure to add pwd to relative paths - getSrc = filepath.Join(req.Pwd, getSrc) - } - return getSrc, true, nil - } // Write back the original source req.Src = originalSrc return "", ok, nil diff --git a/get.go b/get.go index 015730e89..43a658fc0 100644 --- a/get.go +++ b/get.go @@ -46,8 +46,6 @@ type Getter interface { // Detect will detect whether the string matches a known pattern to // turn it into a proper URL. Detect(*Request) (string, bool, error) - - ValidScheme(string) bool } // Getters is the mapping of scheme to the Getter implementation that will diff --git a/get_file.go b/get_file.go index 05540e748..a63b8c8fe 100644 --- a/get_file.go +++ b/get_file.go @@ -12,7 +12,6 @@ import ( // FileGetter is a Getter implementation that will download a module from // a file scheme. type FileGetter struct { - next Getter } func (g *FileGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -159,10 +158,27 @@ func (g *FileGetter) Detect(req *Request) (string, bool, error) { return "", false, nil } + if req.forced != "" { + // There's a getter being forced + if !g.validScheme(req.forced) { + // Current getter is not the forced one + // Don't use it to try to download the artifact + return "", false, nil + } + } + isForcedGetter := req.forced != "" && g.validScheme(req.forced) + u, err := url.Parse(src) - if err == nil && u.Scheme == "file" { - // Valid URL - return src, true, nil + if err == nil && u.Scheme != "" { + if isForcedGetter { + // Is the forced getter and source is a valid url + return src, true, nil + } + if g.validScheme(u.Scheme) { + return src, true, nil + } + // Valid url with a scheme that is not valid for current getter + return "", false, nil } if !filepath.IsAbs(src) { @@ -202,7 +218,7 @@ func (g *FileGetter) Detect(req *Request) (string, bool, error) { return fmtFileURL(src), true, nil } -func (g *FileGetter) ValidScheme(scheme string) bool { +func (g *FileGetter) validScheme(scheme string) bool { return scheme == "file" } diff --git a/get_gcs.go b/get_gcs.go index f72808df6..48578e08f 100644 --- a/get_gcs.go +++ b/get_gcs.go @@ -15,7 +15,6 @@ import ( // GCSGetter is a Getter implementation that will download a module from // a GCS bucket. type GCSGetter struct { - next Getter } func (g *GCSGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -172,16 +171,42 @@ func (g *GCSGetter) Detect(req *Request) (string, bool, error) { return "", false, nil } + if req.forced != "" { + // There's a getter being forced + if !g.validScheme(req.forced) { + // Current getter is not the forced one + // Don't use it to try to download the artifact + return "", false, nil + } + } + isForcedGetter := req.forced != "" && g.validScheme(req.forced) + u, err := url.Parse(src) - if err == nil && u.Scheme == "gcs" { - // Valid URL - return src, true, nil + if err == nil && u.Scheme != "" { + if isForcedGetter { + // Is the forced getter and source is a valid url + return src, true, nil + } + if g.validScheme(u.Scheme) { + return src, true, nil + } + // Valid url with a scheme that is not valid for current getter + return "", false, nil } if strings.Contains(src, "googleapis.com/") { return g.detectHTTP(src) } + if isForcedGetter { + // Is the forced getter and should be used to download the artifact + if req.Pwd != "" && !filepath.IsAbs(src) { + // Make sure to add pwd to relative paths + src = filepath.Join(req.Pwd, src) + } + return src, true, nil + } + return "", false, nil } @@ -205,6 +230,6 @@ func (g *GCSGetter) detectHTTP(src string) (string, bool, error) { return url.String(), true, nil } -func (g *GCSGetter) ValidScheme(scheme string) bool { +func (g *GCSGetter) validScheme(scheme string) bool { return scheme == "gcs" } diff --git a/get_git.go b/get_git.go index 8107a1290..2cc6309b8 100644 --- a/get_git.go +++ b/get_git.go @@ -25,7 +25,6 @@ import ( // GitGetter is a Getter implementation that will download a module from // a git repository. type GitGetter struct { - next Getter } var defaultBranchRegexp = regexp.MustCompile(`\s->\sorigin/(.*)`) @@ -323,10 +322,27 @@ func (g *GitGetter) Detect(req *Request) (string, bool, error) { return "", false, nil } + if req.forced != "" { + // There's a getter being forced + if !g.validScheme(req.forced) { + // Current getter is not the forced one + // Don't use it to try to download the artifact + return "", false, nil + } + } + isForcedGetter := req.forced != "" && g.validScheme(req.forced) + u, err := url.Parse(src) - if err == nil && (u.Scheme == "git" || u.Scheme == "ssh") { - // Valid URL - return src, true, nil + if err == nil && u.Scheme != "" { + if isForcedGetter { + // Is the forced getter and source is a valid url + return src, true, nil + } + if g.validScheme(u.Scheme) { + return src, true, nil + } + // Valid url with a scheme that is not valid for current getter + return "", false, nil } u, err = detectSSH(src) @@ -363,6 +379,15 @@ func (g *GitGetter) Detect(req *Request) (string, bool, error) { return "", true, nil } + if isForcedGetter { + // Is the forced getter and should be used to download the artifact + if req.Pwd != "" && !filepath.IsAbs(src) { + // Make sure to add pwd to relative paths + src = filepath.Join(req.Pwd, src) + } + return src, true, nil + } + return "", false, nil } @@ -427,6 +452,6 @@ func detectGitHub(src string) (string, bool, error) { return url.String(), true, nil } -func (g *GitGetter) ValidScheme(scheme string) bool { +func (g *GitGetter) validScheme(scheme string) bool { return scheme == "git" || scheme == "ssh" } diff --git a/get_hg.go b/get_hg.go index a58341ae5..946581c1a 100644 --- a/get_hg.go +++ b/get_hg.go @@ -15,7 +15,6 @@ import ( // HgGetter is a Getter implementation that will download a module from // a Mercurial repository. type HgGetter struct { - next Getter } func (g *HgGetter) Mode(ctx context.Context, _ *url.URL) (Mode, error) { @@ -131,10 +130,27 @@ func (g *HgGetter) Detect(req *Request) (string, bool, error) { return "", false, nil } + if req.forced != "" { + // There's a getter being forced + if !g.validScheme(req.forced) { + // Current getter is not the forced one + // Don't use it to try to download the artifact + return "", false, nil + } + } + isForcedGetter := req.forced != "" && g.validScheme(req.forced) + u, err := url.Parse(src) - if err == nil && u.Scheme == "hg" { - // Valid URL - return src, true, nil + if err == nil && u.Scheme != "" { + if isForcedGetter { + // Is the forced getter and source is a valid url + return src, true, nil + } + if g.validScheme(u.Scheme) { + return src, true, nil + } + // Valid url with a scheme that is not valid for current getter + return "", false, nil } result, u, err := detectBitBucket(src) @@ -145,10 +161,19 @@ func (g *HgGetter) Detect(req *Request) (string, bool, error) { return u.String(), true, nil } + if isForcedGetter { + // Is the forced getter and should be used to download the artifact + if req.Pwd != "" && !filepath.IsAbs(src) { + // Make sure to add pwd to relative paths + src = filepath.Join(req.Pwd, src) + } + return src, true, nil + } + return "", false, nil } -func (g *HgGetter) ValidScheme(scheme string) bool { +func (g *HgGetter) validScheme(scheme string) bool { return scheme == "hg" } diff --git a/get_http.go b/get_http.go index 774f03f31..afe8665ea 100644 --- a/get_http.go +++ b/get_http.go @@ -48,8 +48,6 @@ type HttpGetter struct { // and as such it needs to be initialized before use, via something like // make(http.Header). Header http.Header - - next Getter } func (g *HttpGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -323,14 +321,45 @@ func charsetReader(charset string, input io.Reader) (io.Reader, error) { func (g *HttpGetter) Detect(req *Request) (string, bool, error) { src := req.Src + if len(src) == 0 { + return "", false, nil + } + + if req.forced != "" { + // There's a getter being forced + if !g.validScheme(req.forced) { + // Current getter is not the forced one + // Don't use it to try to download the artifact + return "", false, nil + } + } + isForcedGetter := req.forced != "" && g.validScheme(req.forced) + u, err := url.Parse(src) - if err == nil && u.Scheme != "" && (u.Scheme == "http" || u.Scheme == "https") { - // Valid URL + if err == nil && u.Scheme != "" { + if isForcedGetter { + // Is the forced getter and source is a valid url + return src, true, nil + } + if g.validScheme(u.Scheme) { + return src, true, nil + } + // Valid url with a scheme that is not valid for current getter + return "", false, nil + } + + if isForcedGetter { + // Is the forced getter and should be used to download the artifact + if req.Pwd != "" && !filepath.IsAbs(src) { + // Make sure to add pwd to relative paths + src = filepath.Join(req.Pwd, src) + } return src, true, nil } + return "", false, nil } -func (g *HttpGetter) ValidScheme(scheme string) bool { +func (g *HttpGetter) validScheme(scheme string) bool { return scheme == "http" || scheme == "https" } diff --git a/get_mock.go b/get_mock.go index 80312e9ed..c651a17b5 100644 --- a/get_mock.go +++ b/get_mock.go @@ -58,10 +58,3 @@ func (g *MockGetter) Detect(req *Request) (string, bool, error) { } return req.Src, true, nil } - -func (g *MockGetter) ValidScheme(scheme string) bool { - if g.Proxy != nil { - return g.Proxy.ValidScheme(scheme) - } - return true -} diff --git a/get_s3.go b/get_s3.go index 423c36f91..a28d6abda 100644 --- a/get_s3.go +++ b/get_s3.go @@ -19,7 +19,6 @@ import ( // S3Getter is a Getter implementation that will download a module from // a S3 bucket. type S3Getter struct { - next Getter } func (g *S3Getter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -278,16 +277,42 @@ func (g *S3Getter) Detect(req *Request) (string, bool, error) { return "", false, nil } + if req.forced != "" { + // There's a getter being forced + if !g.validScheme(req.forced) { + // Current getter is not the forced one + // Don't use it to try to download the artifact + return "", false, nil + } + } + isForcedGetter := req.forced != "" && g.validScheme(req.forced) + u, err := url.Parse(src) - if err == nil && u.Scheme == "s3" { - // Valid URL - return src, true, nil + if err == nil && u.Scheme != "" { + if isForcedGetter { + // Is the forced getter and source is a valid url + return src, true, nil + } + if g.validScheme(u.Scheme) { + return src, true, nil + } + // Valid url with a scheme that is not valid for current getter + return "", false, nil } if strings.Contains(src, ".amazonaws.com/") { return g.detectHTTP(src) } + if isForcedGetter { + // Is the forced getter and should be used to download the artifact + if req.Pwd != "" && !filepath.IsAbs(src) { + // Make sure to add pwd to relative paths + src = filepath.Join(req.Pwd, src) + } + return src, true, nil + } + return "", false, nil } @@ -329,6 +354,6 @@ func (g *S3Getter) detectVhostStyle(region, bucket string, parts []string) (stri return url.String(), true, nil } -func (g *S3Getter) ValidScheme(scheme string) bool { +func (g *S3Getter) validScheme(scheme string) bool { return scheme == "s3" } diff --git a/get_smb_mount.go b/get_smb_mount.go index 293d666de..aa00643ec 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -14,7 +14,6 @@ import ( // SmbMountGetter is a Getter implementation that will download a module from // a shared folder using the file system. type SmbMountGetter struct { - next Getter } func (g *SmbMountGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -208,15 +207,41 @@ func (g *SmbMountGetter) Detect(req *Request) (string, bool, error) { return "", false, nil } + if req.forced != "" { + // There's a getter being forced + if !g.validScheme(req.forced) { + // Current getter is not the forced one + // Don't use it to try to download the artifact + return "", false, nil + } + } + isForcedGetter := req.forced != "" && g.validScheme(req.forced) + u, err := url.Parse(src) - if err == nil && u.Scheme == "smb" { - // Valid URL + if err == nil && u.Scheme != "" { + if isForcedGetter { + // Is the forced getter and source is a valid url + return src, true, nil + } + if g.validScheme(u.Scheme) { + return src, true, nil + } + // Valid url with a scheme that is not valid for current getter + return "", false, nil + } + + if isForcedGetter { + // Is the forced getter and should be used to download the artifact + if req.Pwd != "" && !filepath.IsAbs(src) { + // Make sure to add pwd to relative paths + src = filepath.Join(req.Pwd, src) + } return src, true, nil } return "", false, nil } -func (g *SmbMountGetter) ValidScheme(scheme string) bool { +func (g *SmbMountGetter) validScheme(scheme string) bool { return scheme == "smb" } diff --git a/get_smbclient.go b/get_smbclient.go index 0b322ae81..c5f5637b4 100644 --- a/get_smbclient.go +++ b/get_smbclient.go @@ -16,7 +16,6 @@ import ( // SmbClientGetter is a Getter implementation that will download a module from // a shared folder using smbclient cli. type SmbClientGetter struct { - next Getter } func (g *SmbClientGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { @@ -289,16 +288,42 @@ func (g *SmbClientGetter) Detect(req *Request) (string, bool, error) { return "", false, nil } + if req.forced != "" { + // There's a getter being forced + if !g.validScheme(req.forced) { + // Current getter is not the forced one + // Don't use it to try to download the artifact + return "", false, nil + } + } + isForcedGetter := req.forced != "" && g.validScheme(req.forced) + u, err := url.Parse(src) - if err == nil && u.Scheme == "smb" { - // Valid URL + if err == nil && u.Scheme != "" { + if isForcedGetter { + // Is the forced getter and source is a valid url + return src, true, nil + } + if g.validScheme(u.Scheme) { + return src, true, nil + } + // Valid url with a scheme that is not valid for current getter + return "", false, nil + } + + if isForcedGetter { + // Is the forced getter and should be used to download the artifact + if req.Pwd != "" && !filepath.IsAbs(src) { + // Make sure to add pwd to relative paths + src = filepath.Join(req.Pwd, src) + } return src, true, nil } return "", false, nil } -func (g *SmbClientGetter) ValidScheme(scheme string) bool { +func (g *SmbClientGetter) validScheme(scheme string) bool { return scheme == "smb" } diff --git a/request.go b/request.go index f8f24d592..5ee11c13d 100644 --- a/request.go +++ b/request.go @@ -16,6 +16,10 @@ type Request struct { Dst string Pwd string + // Forced getter detected in Src during Getter detection phase + // This is currently for internal use only + forced string + // Mode is the method of download the client will use. See Mode // for documentation. Mode Mode From 9608b604facffdb9bce70613bb7e6fc160a64ff3 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 7 May 2020 15:56:44 +0200 Subject: [PATCH 086/109] change error handling --- client.go | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/client.go b/client.go index 387d535f6..a3d218ff0 100644 --- a/client.go +++ b/client.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "strconv" "strings" @@ -46,9 +47,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { req.Mode = ModeAny } - var modeErr *multierror.Error - var getFileErr *multierror.Error - var getErr *multierror.Error + var multierr *multierror.Error for _, g := range c.Getters { src, ok, err := Detect(req, g) if err != nil { @@ -58,6 +57,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { continue } req.Src = src + getterName := reflect.Indirect(reflect.ValueOf(g)).Type().Name() // If there is a subdir component, then we download the root separately // and then copy over the proper subdir. @@ -147,7 +147,8 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // Ask the getter which client mode to use req.Mode, err = g.Mode(ctx, req.u) if err != nil { - modeErr = multierror.Append(modeErr, err) + err = fmt.Errorf("%s failed: %s", getterName, err.Error()) + multierr = multierror.Append(multierr, err) continue } @@ -182,7 +183,8 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { if getFile { err := g.GetFile(ctx, req) if err != nil { - getFileErr = multierror.Append(getFileErr, err) + err = fmt.Errorf("%s failed: %s", getterName, err.Error()) + multierr = multierror.Append(multierr, err) continue } @@ -234,7 +236,8 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // if we're specifying a subdir. err := g.Get(ctx, req) if err != nil { - getErr = multierror.Append(getErr, err) + err = fmt.Errorf("%s failed: %s", getterName, err.Error()) + multierr = multierror.Append(multierr, err) continue } } @@ -261,15 +264,10 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // If there's an getErr or getFileErr, we can ignore any modeErr because is // going to be a getter that didn't get to far on downloading the artifact - if getErr != nil { - return nil, fmt.Errorf("error downloading '%s': %s", req.Src, getErr) - } - if getFileErr != nil { - return nil, fmt.Errorf("error downloading '%s': %s", req.Src, getFileErr) - } - if modeErr != nil { - return nil, fmt.Errorf("error downloading '%s': %s", req.Src, modeErr) + if multierr != nil { + return nil, fmt.Errorf("error downloading '%s': %s", req.Src, multierr) } + return nil, fmt.Errorf("error downloading '%s'", req.Src) } From 227cd74c11b8b00630bc1080467fd7cfe15f8934 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 7 May 2020 16:29:22 +0200 Subject: [PATCH 087/109] fix path slash for windows --- get_gcs.go | 3 ++- get_git.go | 3 ++- get_hg.go | 3 ++- get_http.go | 3 ++- get_s3.go | 3 ++- get_smb_mount.go | 3 ++- get_smbclient.go | 3 ++- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/get_gcs.go b/get_gcs.go index 48578e08f..6c09a37cc 100644 --- a/get_gcs.go +++ b/get_gcs.go @@ -204,7 +204,8 @@ func (g *GCSGetter) Detect(req *Request) (string, bool, error) { // Make sure to add pwd to relative paths src = filepath.Join(req.Pwd, src) } - return src, true, nil + // Make sure we're using "/" on Windows. URLs are "/"-based. + return filepath.ToSlash(src), true, nil } return "", false, nil diff --git a/get_git.go b/get_git.go index 2cc6309b8..daa38f43e 100644 --- a/get_git.go +++ b/get_git.go @@ -385,7 +385,8 @@ func (g *GitGetter) Detect(req *Request) (string, bool, error) { // Make sure to add pwd to relative paths src = filepath.Join(req.Pwd, src) } - return src, true, nil + // Make sure we're using "/" on Windows. URLs are "/"-based. + return filepath.ToSlash(src), true, nil } return "", false, nil diff --git a/get_hg.go b/get_hg.go index 946581c1a..3245f9b0b 100644 --- a/get_hg.go +++ b/get_hg.go @@ -167,7 +167,8 @@ func (g *HgGetter) Detect(req *Request) (string, bool, error) { // Make sure to add pwd to relative paths src = filepath.Join(req.Pwd, src) } - return src, true, nil + // Make sure we're using "/" on Windows. URLs are "/"-based. + return filepath.ToSlash(src), true, nil } return "", false, nil diff --git a/get_http.go b/get_http.go index afe8665ea..46c936236 100644 --- a/get_http.go +++ b/get_http.go @@ -354,7 +354,8 @@ func (g *HttpGetter) Detect(req *Request) (string, bool, error) { // Make sure to add pwd to relative paths src = filepath.Join(req.Pwd, src) } - return src, true, nil + // Make sure we're using "/" on Windows. URLs are "/"-based. + return filepath.ToSlash(src), true, nil } return "", false, nil diff --git a/get_s3.go b/get_s3.go index a28d6abda..6b8770dee 100644 --- a/get_s3.go +++ b/get_s3.go @@ -310,7 +310,8 @@ func (g *S3Getter) Detect(req *Request) (string, bool, error) { // Make sure to add pwd to relative paths src = filepath.Join(req.Pwd, src) } - return src, true, nil + // Make sure we're using "/" on Windows. URLs are "/"-based. + return filepath.ToSlash(src), true, nil } return "", false, nil diff --git a/get_smb_mount.go b/get_smb_mount.go index aa00643ec..63303555d 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -236,7 +236,8 @@ func (g *SmbMountGetter) Detect(req *Request) (string, bool, error) { // Make sure to add pwd to relative paths src = filepath.Join(req.Pwd, src) } - return src, true, nil + // Make sure we're using "/" on Windows. URLs are "/"-based. + return filepath.ToSlash(src), true, nil } return "", false, nil diff --git a/get_smbclient.go b/get_smbclient.go index c5f5637b4..3f3c0f786 100644 --- a/get_smbclient.go +++ b/get_smbclient.go @@ -317,7 +317,8 @@ func (g *SmbClientGetter) Detect(req *Request) (string, bool, error) { // Make sure to add pwd to relative paths src = filepath.Join(req.Pwd, src) } - return src, true, nil + // Make sure we're using "/" on Windows. URLs are "/"-based. + return filepath.ToSlash(src), true, nil } return "", false, nil From 816685aac7f22e8bc98be7729cb4e7bbd16a3d7e Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 7 May 2020 16:36:18 +0200 Subject: [PATCH 088/109] Update Request on detection --- client.go | 3 +-- detector.go | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index a3d218ff0..88a9edb8a 100644 --- a/client.go +++ b/client.go @@ -49,14 +49,13 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { var multierr *multierror.Error for _, g := range c.Getters { - src, ok, err := Detect(req, g) + ok, err := Detect(req, g) if err != nil { return nil, err } if !ok { continue } - req.Src = src getterName := reflect.Indirect(reflect.ValueOf(g)).Type().Name() // If there is a subdir component, then we download the root separately diff --git a/detector.go b/detector.go index 7c08f4403..9df8c08ff 100644 --- a/detector.go +++ b/detector.go @@ -9,7 +9,7 @@ import ( // Detect is a method used to detect if a Getter is a candidate for downloading and artifact // by validating if a source string is detected to be of a known pattern, // and to transform it to a known pattern when necessary. -func Detect(req *Request, getter Getter) (string, bool, error) { +func Detect(req *Request, getter Getter) (bool, error) { originalSrc := req.Src getForce, getSrc := getForcedGetter(req.Src) @@ -21,12 +21,12 @@ func Detect(req *Request, getter Getter) (string, bool, error) { req.Src = getSrc result, ok, err := getter.Detect(req) if err != nil { - return "", true, err + return true, err } if !ok { // Write back the original source req.Src = originalSrc - return "", ok, nil + return ok, nil } result, detectSubdir := SourceDirSubdir(result) @@ -44,7 +44,7 @@ func Detect(req *Request, getter Getter) (string, bool, error) { if subDir != "" { u, err := url.Parse(result) if err != nil { - return "", true, fmt.Errorf("Error parsing URL: %s", err) + return true, fmt.Errorf("Error parsing URL: %s", err) } u.Path += "//" + subDir @@ -55,5 +55,6 @@ func Detect(req *Request, getter Getter) (string, bool, error) { result = u.String() } - return result, true, nil + req.Src = result + return true, nil } From 00cab945980940640cd4df78a67252feefdad860 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 7 May 2020 17:46:11 +0200 Subject: [PATCH 089/109] Fix tests --- detect_bitbucket_test.go | 7 +++---- detect_file_test.go | 12 ++++++------ detect_gcs_test.go | 6 +++--- detect_git_test.go | 6 +++--- detect_github_test.go | 6 +++--- detect_s3_test.go | 6 +++--- detector.go | 1 + detector_test.go | 6 +++--- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/detect_bitbucket_test.go b/detect_bitbucket_test.go index 9296e53c9..dc3c3d1ed 100644 --- a/detect_bitbucket_test.go +++ b/detect_bitbucket_test.go @@ -43,13 +43,12 @@ func TestBitBucketDetector(t *testing.T) { for i, tc := range cases { var err error for i := 0; i < 3; i++ { - var output string var ok bool req := &Request{ Src: tc.Input, Pwd: pwd, } - output, ok, err = Detect(req, tc.g) + ok, err = Detect(req, tc.g) if err != nil { if strings.Contains(err.Error(), "invalid character") { continue @@ -61,8 +60,8 @@ func TestBitBucketDetector(t *testing.T) { t.Fatal("not ok") } - if output != tc.Output { - t.Fatalf("%d: bad: %#v", i, output) + if req.Src != tc.Output { + t.Fatalf("%d: bad: %#v", i, req.Src) } break diff --git a/detect_file_test.go b/detect_file_test.go index 2d4f904db..32f65ef85 100644 --- a/detect_file_test.go +++ b/detect_file_test.go @@ -59,7 +59,7 @@ func TestFileDetector(t *testing.T) { Src: tc.in, Pwd: pwd, } - out, ok, err := Detect(req, f) + ok, err := Detect(req, f) if err != nil { t.Fatalf("err: %s", err) } @@ -72,9 +72,9 @@ func TestFileDetector(t *testing.T) { expected = filepath.Join(pwdRoot, expected) } - if out != expected { + if req.Src != expected { t.Fatalf("input: %q\npwd: %q\nexpected: %q\nbad output: %#v", - tc.in, pwd, expected, out) + tc.in, pwd, expected, req.Src) } }) } @@ -107,7 +107,7 @@ func TestFileDetector_noPwd(t *testing.T) { Src: tc.in, Pwd: tc.pwd, } - out, ok, err := Detect(req, f) + ok, err := Detect(req, f) if err != nil != tc.err { t.Fatalf("%d: err: %s", i, err) } @@ -115,8 +115,8 @@ func TestFileDetector_noPwd(t *testing.T) { t.Fatal("not ok") } - if out != tc.out { - t.Fatalf("%d: bad: %#v", i, out) + if req.Src != tc.out { + t.Fatalf("%d: bad: %#v", i, req.Src) } } } diff --git a/detect_gcs_test.go b/detect_gcs_test.go index 5f2c00002..ecd9a26c0 100644 --- a/detect_gcs_test.go +++ b/detect_gcs_test.go @@ -30,7 +30,7 @@ func TestGCSDetector(t *testing.T) { Src: tc.Input, Pwd: pwd, } - output, ok, err := Detect(req, f) + ok, err := Detect(req, f) if err != nil { t.Fatalf("err: %s", err) } @@ -38,8 +38,8 @@ func TestGCSDetector(t *testing.T) { t.Fatal("not ok") } - if output != tc.Output { - t.Fatalf("%d: bad: %#v", i, output) + if req.Src != tc.Output { + t.Fatalf("%d: bad: %#v", i, req.Src) } } } diff --git a/detect_git_test.go b/detect_git_test.go index 5a882bae7..0725b6761 100644 --- a/detect_git_test.go +++ b/detect_git_test.go @@ -54,12 +54,12 @@ func TestGitDetector(t *testing.T) { Src: tc.Input, Pwd: pwd, } - output, _, err := Detect(req, new(GitGetter)) + _, err := Detect(req, new(GitGetter)) if err != nil { t.Fatalf("unexpected error: %s", err) } - if output != tc.Output { - t.Errorf("wrong result\ninput: %s\ngot: %s\nwant: %s", tc.Input, output, tc.Output) + if req.Src != tc.Output { + t.Errorf("wrong result\ninput: %s\ngot: %s\nwant: %s", tc.Input, req.Src, tc.Output) } }) } diff --git a/detect_github_test.go b/detect_github_test.go index c1a45d547..714d30986 100644 --- a/detect_github_test.go +++ b/detect_github_test.go @@ -33,7 +33,7 @@ func TestGitHubDetector(t *testing.T) { Src: tc.Input, Pwd: pwd, } - output, ok, err := Detect(req, f) + ok, err := Detect(req, f) if err != nil { t.Fatalf("err: %s", err) } @@ -41,8 +41,8 @@ func TestGitHubDetector(t *testing.T) { t.Fatal("not ok") } - if output != tc.Output { - t.Fatalf("%d: bad: %#v", i, output) + if req.Src != tc.Output { + t.Fatalf("%d: bad: %#v", i, req.Src) } } } diff --git a/detect_s3_test.go b/detect_s3_test.go index adec07812..7167d463d 100644 --- a/detect_s3_test.go +++ b/detect_s3_test.go @@ -73,7 +73,7 @@ func TestS3Detector(t *testing.T) { Src: tc.Input, Pwd: pwd, } - output, ok, err := Detect(req, f) + ok, err := Detect(req, f) if err != nil { t.Fatalf("err: %s", err) } @@ -81,8 +81,8 @@ func TestS3Detector(t *testing.T) { t.Fatal("not ok") } - if output != tc.Output { - t.Fatalf("%d: bad: %#v", i, output) + if req.Src != tc.Output { + t.Fatalf("%d: bad: %#v", i, req.Src) } } } diff --git a/detector.go b/detector.go index 9df8c08ff..ff76ce8d5 100644 --- a/detector.go +++ b/detector.go @@ -21,6 +21,7 @@ func Detect(req *Request, getter Getter) (bool, error) { req.Src = getSrc result, ok, err := getter.Detect(req) if err != nil { + req.Src = result return true, err } if !ok { diff --git a/detector_test.go b/detector_test.go index b8679b393..5d7bc911c 100644 --- a/detector_test.go +++ b/detector_test.go @@ -95,12 +95,12 @@ func TestDetect(t *testing.T) { Src: tc.Input, Pwd: tc.Pwd, } - output, _, err := Detect(req, tc.getter) + _, err := Detect(req, tc.getter) 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) + if req.Src != tc.Output { + t.Fatalf("%d: bad output: %s\nexpected: %s", i, req.Src, tc.Output) } }) } From 5f3ee0e867407108351b12bcc7bd5c96fcade456 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 7 May 2020 17:59:20 +0200 Subject: [PATCH 090/109] Fix error msg --- client.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index 88a9edb8a..9374d122e 100644 --- a/client.go +++ b/client.go @@ -67,6 +67,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { if err != nil { return nil, err } + // TODO @sylviamoss defer doesn't work here inside a for loop defer tdcloser.Close() realDst = req.Dst @@ -119,6 +120,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { return nil, fmt.Errorf( "Error creating temporary directory for archive: %s", err) } + // TODO @sylviamoss defer doesn't work here inside a for loop defer os.RemoveAll(td) // Swap the download directory to be our temporary path and @@ -180,8 +182,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { } } if getFile { - err := g.GetFile(ctx, req) - if err != nil { + if err := g.GetFile(ctx, req); err != nil { err = fmt.Errorf("%s failed: %s", getterName, err.Error()) multierr = multierror.Append(multierr, err) continue @@ -233,8 +234,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // We're downloading a directory, which might require a bit more work // if we're specifying a subdir. - err := g.Get(ctx, req) - if err != nil { + if err := g.Get(ctx, req); err != nil { err = fmt.Errorf("%s failed: %s", getterName, err.Error()) multierr = multierror.Append(multierr, err) continue @@ -261,10 +261,8 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { return &GetResult{req.Dst}, nil } - // If there's an getErr or getFileErr, we can ignore any modeErr because is - // going to be a getter that didn't get to far on downloading the artifact if multierr != nil { - return nil, fmt.Errorf("error downloading '%s': %s", req.Src, multierr) + return nil, fmt.Errorf("error downloading '%s': %s", req.Src, multierr.Error()) } return nil, fmt.Errorf("error downloading '%s'", req.Src) From 73343662bca9efbc6f13050e6b3c199d240388a2 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 11 May 2020 14:34:12 +0200 Subject: [PATCH 091/109] fix fmt --- client.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client.go b/client.go index 91d2bccba..b2f949b05 100644 --- a/client.go +++ b/client.go @@ -131,11 +131,11 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { req.Mode = ModeFile } - // Determine checksum if we have one - checksum, err := c.GetChecksum(ctx, req) - if err != nil { - return nil, fmt.Errorf("invalid checksum: %s", err) - } + // Determine checksum if we have one + checksum, err := c.GetChecksum(ctx, req) + if err != nil { + return nil, fmt.Errorf("invalid checksum: %s", err) + } // Delete the query parameter if we have it. q.Del("checksum") From 8af93735d41121e4b73f4a412bc4a7169e8cd25c Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 11 May 2020 14:39:55 +0200 Subject: [PATCH 092/109] Fix build fail --- gcs/detect_gcs_test.go | 46 -------------- gcs/get_gcs_test.go | 40 +++++++++++++ get_smb_mount.go | 133 ++--------------------------------------- s3/detect_s3_test.go | 89 --------------------------- s3/get_s3_test.go | 91 ++++++++++++++++++++++++++-- 5 files changed, 133 insertions(+), 266 deletions(-) delete mode 100644 gcs/detect_gcs_test.go delete mode 100644 s3/detect_s3_test.go diff --git a/gcs/detect_gcs_test.go b/gcs/detect_gcs_test.go deleted file mode 100644 index 9023a919d..000000000 --- a/gcs/detect_gcs_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package gcs - -import ( - "github.com/hashicorp/go-getter/v2" - "testing" -) - -func TestGCSDetector(t *testing.T) { - cases := []struct { - Input string - Output string - }{ - { - "www.googleapis.com/storage/v1/bucket/foo", - "https://www.googleapis.com/storage/v1/bucket/foo", - }, - { - "www.googleapis.com/storage/v1/bucket/foo/bar", - "https://www.googleapis.com/storage/v1/bucket/foo/bar", - }, - { - "www.googleapis.com/storage/v1/foo/bar.baz", - "https://www.googleapis.com/storage/v1/foo/bar.baz", - }, - } - - pwd := "/pwd" - f := new(Getter) - for i, tc := range cases { - req := &getter.Request{ - Src: tc.Input, - Pwd: pwd, - } - ok, err := getter.Detect(req, f) - if err != nil { - t.Fatalf("err: %s", err) - } - if !ok { - t.Fatal("not ok") - } - - if req.Src != tc.Output { - t.Fatalf("%d: bad: %#v", i, req.Src) - } - } -} diff --git a/gcs/get_gcs_test.go b/gcs/get_gcs_test.go index 38893db00..31f860a4e 100644 --- a/gcs/get_gcs_test.go +++ b/gcs/get_gcs_test.go @@ -238,3 +238,43 @@ func TestGetter_Url(t *testing.T) { }) } } + +func TestGetter_Detect(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + { + "www.googleapis.com/storage/v1/bucket/foo", + "https://www.googleapis.com/storage/v1/bucket/foo", + }, + { + "www.googleapis.com/storage/v1/bucket/foo/bar", + "https://www.googleapis.com/storage/v1/bucket/foo/bar", + }, + { + "www.googleapis.com/storage/v1/foo/bar.baz", + "https://www.googleapis.com/storage/v1/foo/bar.baz", + }, + } + + pwd := "/pwd" + f := new(Getter) + for i, tc := range cases { + req := &getter.Request{ + Src: tc.Input, + Pwd: pwd, + } + ok, err := getter.Detect(req, f) + if err != nil { + t.Fatalf("err: %s", err) + } + if !ok { + t.Fatal("not ok") + } + + if req.Src != tc.Output { + t.Fatalf("%d: bad: %#v", i, req.Src) + } + } +} diff --git a/get_smb_mount.go b/get_smb_mount.go index 6e3aa85ee..b6b21dbb9 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -22,23 +22,9 @@ func (g *SmbMountGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { } prefix, path := g.findPrefixAndPath(u) - path = prefix + path + u.Path = prefix + path - if u.RawPath != "" { - path = u.RawPath - } - - fi, err := os.Stat(path) - if err != nil { - return 0, err - } - - // Check if the source is a directory. - if fi.IsDir() { - return ModeDir, nil - } - - return ModeFile, nil + return new(FileGetter).Mode(ctx, u) } func (g *SmbMountGetter) Get(ctx context.Context, req *Request) error { @@ -47,48 +33,9 @@ func (g *SmbMountGetter) Get(ctx context.Context, req *Request) error { } prefix, path := g.findPrefixAndPath(req.u) - path = prefix + path - - if req.u.RawPath != "" { - path = req.u.RawPath - } - - // The source path must exist and be a directory to be usable. - if fi, err := os.Stat(path); err != nil { - return fmt.Errorf("source path error: %s", err) - } else if !fi.IsDir() { - return fmt.Errorf("source path must be a directory") - } - - fi, err := os.Lstat(req.Dst) - if err != nil && !os.IsNotExist(err) { - return err - } - - if req.Inplace { - req.Dst = path - return nil - } + req.u.Path = prefix + path - // If the destination already exists, it must be a symlink - if err == nil { - mode := fi.Mode() - if mode&os.ModeSymlink == 0 { - return fmt.Errorf("destination exists and is not a symlink") - } - - // Remove the destination - if err := os.Remove(req.Dst); err != nil { - return err - } - } - - // Create all the parent directories - if err := os.MkdirAll(filepath.Dir(req.Dst), 0755); err != nil { - return err - } - - return SymlinkAny(path, req.Dst) + return new(FileGetter).Get(ctx, req) } func (g *SmbMountGetter) GetFile(ctx context.Context, req *Request) error { @@ -97,77 +44,9 @@ func (g *SmbMountGetter) GetFile(ctx context.Context, req *Request) error { } prefix, path := g.findPrefixAndPath(req.u) - path = prefix + path - - if req.u.RawPath != "" { - path = req.u.RawPath - } - - // The source path must exist and be a file to be usable. - if fi, err := os.Stat(path); err != nil { - return fmt.Errorf("source path error: %s", err) - } else if fi.IsDir() { - return fmt.Errorf("source path must be a file") - } - - if req.Inplace { - req.Dst = path - return nil - } - - _, err := os.Lstat(req.Dst) - if err != nil && !os.IsNotExist(err) { - return err - } - - // If the destination already exists, it must be a symlink - if err == nil { - // Remove the destination - if err := os.Remove(req.Dst); err != nil { - return err - } - } - - // Create all the parent directories - if err := os.MkdirAll(filepath.Dir(req.Dst), 0755); err != nil { - return err - } - - // If we're not copying, just symlink and we're done - if !req.Copy { - if err = os.Symlink(path, req.Dst); err == nil { - return err - } - lerr, ok := err.(*os.LinkError) - if !ok { - return err - } - switch lerr.Err { - case ErrUnauthorized: - // On windows this means we don't have - // symlink privilege, let's - // fallback to a copy to avoid an error. - break - default: - return err - } - } - - // Copy - srcF, err := os.Open(path) - if err != nil { - return err - } - defer srcF.Close() - - dstF, err := os.Create(req.Dst) - if err != nil { - return err - } - defer dstF.Close() + req.u.Path = prefix + path - _, err = Copy(ctx, dstF, srcF) - return err + return new(FileGetter).GetFile(ctx, req) } func (g *SmbMountGetter) findPrefixAndPath(u *url.URL) (string, string) { diff --git a/s3/detect_s3_test.go b/s3/detect_s3_test.go deleted file mode 100644 index 2829ec04f..000000000 --- a/s3/detect_s3_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package s3 - -import ( - "github.com/hashicorp/go-getter/v2" - "testing" -) - -func TestS3Detector(t *testing.T) { - cases := []struct { - Input string - Output string - }{ - // Virtual hosted style - { - "bucket.s3.amazonaws.com/foo", - "https://s3.amazonaws.com/bucket/foo", - }, - { - "bucket.s3.amazonaws.com/foo/bar", - "https://s3.amazonaws.com/bucket/foo/bar", - }, - { - "bucket.s3.amazonaws.com/foo/bar.baz", - "https://s3.amazonaws.com/bucket/foo/bar.baz", - }, - { - "bucket.s3-eu-west-1.amazonaws.com/foo", - "https://s3-eu-west-1.amazonaws.com/bucket/foo", - }, - { - "bucket.s3-eu-west-1.amazonaws.com/foo/bar", - "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", - }, - { - "bucket.s3-eu-west-1.amazonaws.com/foo/bar.baz", - "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", - }, - // Path style - { - "s3.amazonaws.com/bucket/foo", - "https://s3.amazonaws.com/bucket/foo", - }, - { - "s3.amazonaws.com/bucket/foo/bar", - "https://s3.amazonaws.com/bucket/foo/bar", - }, - { - "s3.amazonaws.com/bucket/foo/bar.baz", - "https://s3.amazonaws.com/bucket/foo/bar.baz", - }, - { - "s3-eu-west-1.amazonaws.com/bucket/foo", - "https://s3-eu-west-1.amazonaws.com/bucket/foo", - }, - { - "s3-eu-west-1.amazonaws.com/bucket/foo/bar", - "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", - }, - { - "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", - "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", - }, - // Misc tests - { - "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", - "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", - }, - } - - pwd := "/pwd" - f := new(Getter) - for i, tc := range cases { - req := &getter.Request{ - Src: tc.Input, - Pwd: pwd, - } - ok, err := getter.Detect(req, f) - if err != nil { - t.Fatalf("err: %s", err) - } - if !ok { - t.Fatal("not ok") - } - - if req.Src != tc.Output { - t.Fatalf("%d: bad: %#v", i, req.Src) - } - } -} diff --git a/s3/get_s3_test.go b/s3/get_s3_test.go index 70f4e70e6..271d49663 100644 --- a/s3/get_s3_test.go +++ b/s3/get_s3_test.go @@ -69,7 +69,7 @@ func TestGetter_subdir(t *testing.T) { } c := getter.Client{ - Getters: map[string]getter.Getter{"s3": g}, + Getters: []getter.Getter{g}, } // With a dir that doesn't exist @@ -99,7 +99,7 @@ func TestGetter_GetFile(t *testing.T) { } c := getter.Client{ - Getters: map[string]getter.Getter{"s3": g}, + Getters: []getter.Getter{g}, } // Download @@ -129,7 +129,7 @@ func TestGetter_GetFile_badParams(t *testing.T) { } c := getter.Client{ - Getters: map[string]getter.Getter{"s3": g}, + Getters: []getter.Getter{g}, } // Download @@ -157,7 +157,7 @@ func TestGetter_GetFile_notfound(t *testing.T) { } c := getter.Client{ - Getters: map[string]getter.Getter{"s3": g}, + Getters: []getter.Getter{g}, } // Download _, err := c.Get(ctx, req) @@ -312,3 +312,86 @@ func TestGetter_Url(t *testing.T) { }) } } + +func TestGetter_Detect(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + // Virtual hosted style + { + "bucket.s3.amazonaws.com/foo", + "https://s3.amazonaws.com/bucket/foo", + }, + { + "bucket.s3.amazonaws.com/foo/bar", + "https://s3.amazonaws.com/bucket/foo/bar", + }, + { + "bucket.s3.amazonaws.com/foo/bar.baz", + "https://s3.amazonaws.com/bucket/foo/bar.baz", + }, + { + "bucket.s3-eu-west-1.amazonaws.com/foo", + "https://s3-eu-west-1.amazonaws.com/bucket/foo", + }, + { + "bucket.s3-eu-west-1.amazonaws.com/foo/bar", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", + }, + { + "bucket.s3-eu-west-1.amazonaws.com/foo/bar.baz", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", + }, + // Path style + { + "s3.amazonaws.com/bucket/foo", + "https://s3.amazonaws.com/bucket/foo", + }, + { + "s3.amazonaws.com/bucket/foo/bar", + "https://s3.amazonaws.com/bucket/foo/bar", + }, + { + "s3.amazonaws.com/bucket/foo/bar.baz", + "https://s3.amazonaws.com/bucket/foo/bar.baz", + }, + { + "s3-eu-west-1.amazonaws.com/bucket/foo", + "https://s3-eu-west-1.amazonaws.com/bucket/foo", + }, + { + "s3-eu-west-1.amazonaws.com/bucket/foo/bar", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", + }, + { + "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", + }, + // Misc tests + { + "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", + }, + } + + pwd := "/pwd" + f := new(Getter) + for i, tc := range cases { + req := &getter.Request{ + Src: tc.Input, + Pwd: pwd, + } + ok, err := getter.Detect(req, f) + if err != nil { + t.Fatalf("err: %s", err) + } + if !ok { + t.Fatal("not ok") + } + + if req.Src != tc.Output { + t.Fatalf("%d: bad: %#v", i, req.Src) + } + } +} From ad53e85972e44760829f167914ed5f3237ef697e Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 11 May 2020 14:52:35 +0200 Subject: [PATCH 093/109] Fix some code organization --- detector.go => detect.go | 4 +- detect_git_test.go | 66 ------------------ detect_github_test.go | 48 ------------- detector_test.go => detect_test.go | 0 get.go | 4 +- get_git_test.go | 104 +++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 119 deletions(-) rename detector.go => detect.go (93%) delete mode 100644 detect_git_test.go delete mode 100644 detect_github_test.go rename detector_test.go => detect_test.go (100%) diff --git a/detector.go b/detect.go similarity index 93% rename from detector.go rename to detect.go index 0ef5be04b..4a5e4b97e 100644 --- a/detector.go +++ b/detect.go @@ -6,9 +6,9 @@ import ( "path/filepath" ) -// Detect is a method used to detect if a Getter is a candidate for downloading and artifact +// Detect is a method used to detect if a Getter is a candidate for downloading an artifact // by validating if a source string is detected to be of a known pattern, -// and to transform it to a known pattern when necessary. +// and transforming it to a known pattern when necessary. func Detect(req *Request, getter Getter) (bool, error) { originalSrc := req.Src diff --git a/detect_git_test.go b/detect_git_test.go deleted file mode 100644 index 0725b6761..000000000 --- a/detect_git_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package getter - -import ( - "testing" -) - -func TestGitDetector(t *testing.T) { - cases := []struct { - Input string - Output string - }{ - { - "git@github.com:hashicorp/foo.git", - "ssh://git@github.com/hashicorp/foo.git", - }, - { - "git@github.com:org/project.git?ref=test-branch", - "ssh://git@github.com/org/project.git?ref=test-branch", - }, - { - "git@github.com:hashicorp/foo.git//bar", - "ssh://git@github.com/hashicorp/foo.git//bar", - }, - { - "git@github.com:hashicorp/foo.git?foo=bar", - "ssh://git@github.com/hashicorp/foo.git?foo=bar", - }, - { - "git@github.xyz.com:org/project.git", - "ssh://git@github.xyz.com/org/project.git", - }, - { - "git@github.xyz.com:org/project.git?ref=test-branch", - "ssh://git@github.xyz.com/org/project.git?ref=test-branch", - }, - { - "git@github.xyz.com:org/project.git//module/a", - "ssh://git@github.xyz.com/org/project.git//module/a", - }, - { - "git@github.xyz.com:org/project.git//module/a?ref=test-branch", - "ssh://git@github.xyz.com/org/project.git//module/a?ref=test-branch", - }, - { - "git::ssh://git@git.example.com:2222/hashicorp/foo.git", - "ssh://git@git.example.com:2222/hashicorp/foo.git", - }, - } - - pwd := "/pwd" - for _, tc := range cases { - t.Run(tc.Input, func(t *testing.T) { - req := &Request{ - Src: tc.Input, - Pwd: pwd, - } - _, err := Detect(req, new(GitGetter)) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - if req.Src != tc.Output { - t.Errorf("wrong result\ninput: %s\ngot: %s\nwant: %s", tc.Input, req.Src, tc.Output) - } - }) - } -} diff --git a/detect_github_test.go b/detect_github_test.go deleted file mode 100644 index 714d30986..000000000 --- a/detect_github_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package getter - -import ( - "testing" -) - -func TestGitHubDetector(t *testing.T) { - cases := []struct { - Input string - Output string - }{ - // HTTP - {"github.com/hashicorp/foo", "https://github.com/hashicorp/foo.git"}, - {"github.com/hashicorp/foo.git", "https://github.com/hashicorp/foo.git"}, - { - "github.com/hashicorp/foo/bar", - "https://github.com/hashicorp/foo.git//bar", - }, - { - "github.com/hashicorp/foo?foo=bar", - "https://github.com/hashicorp/foo.git?foo=bar", - }, - { - "github.com/hashicorp/foo.git?foo=bar", - "https://github.com/hashicorp/foo.git?foo=bar", - }, - } - - pwd := "/pwd" - f := new(GitGetter) - for i, tc := range cases { - req := &Request{ - Src: tc.Input, - Pwd: pwd, - } - ok, err := Detect(req, f) - if err != nil { - t.Fatalf("err: %s", err) - } - if !ok { - t.Fatal("not ok") - } - - if req.Src != tc.Output { - t.Fatalf("%d: bad: %#v", i, req.Src) - } - } -} diff --git a/detector_test.go b/detect_test.go similarity index 100% rename from detector_test.go rename to detect_test.go diff --git a/get.go b/get.go index a4686ba48..deb1af5c4 100644 --- a/get.go +++ b/get.go @@ -72,8 +72,6 @@ func init() { Getters = []Getter{ new(GitGetter), new(HgGetter), - //new(S3Getter), - //new(GCSGetter), new(FileGetter), new(SmbClientGetter), new(SmbMountGetter), @@ -145,7 +143,7 @@ func getRunCommand(cmd *exec.Cmd) error { return fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) } -// getForcedGetter takes a source and returns the tuple of the Forced +// getForcedGetter takes a source and returns the tuple of the forced // getter and the raw URL (without the force syntax). // For example "git::https://...". returns "git" "https://". func getForcedGetter(src string) (string, string) { diff --git a/get_git_test.go b/get_git_test.go index ec3f35eee..8ea60b16b 100644 --- a/get_git_test.go +++ b/get_git_test.go @@ -594,6 +594,110 @@ func TestGitGetter_setupGitEnvWithExisting_sshKey(t *testing.T) { } } +func TestGitGetter_GitHubDetector(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + // HTTP + {"github.com/hashicorp/foo", "https://github.com/hashicorp/foo.git"}, + {"github.com/hashicorp/foo.git", "https://github.com/hashicorp/foo.git"}, + { + "github.com/hashicorp/foo/bar", + "https://github.com/hashicorp/foo.git//bar", + }, + { + "github.com/hashicorp/foo?foo=bar", + "https://github.com/hashicorp/foo.git?foo=bar", + }, + { + "github.com/hashicorp/foo.git?foo=bar", + "https://github.com/hashicorp/foo.git?foo=bar", + }, + } + + pwd := "/pwd" + f := new(GitGetter) + for i, tc := range cases { + req := &Request{ + Src: tc.Input, + Pwd: pwd, + } + ok, err := Detect(req, f) + if err != nil { + t.Fatalf("err: %s", err) + } + if !ok { + t.Fatal("not ok") + } + + if req.Src != tc.Output { + t.Fatalf("%d: bad: %#v", i, req.Src) + } + } +} + +func TestGitGetter_Detector(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + { + "git@github.com:hashicorp/foo.git", + "ssh://git@github.com/hashicorp/foo.git", + }, + { + "git@github.com:org/project.git?ref=test-branch", + "ssh://git@github.com/org/project.git?ref=test-branch", + }, + { + "git@github.com:hashicorp/foo.git//bar", + "ssh://git@github.com/hashicorp/foo.git//bar", + }, + { + "git@github.com:hashicorp/foo.git?foo=bar", + "ssh://git@github.com/hashicorp/foo.git?foo=bar", + }, + { + "git@github.xyz.com:org/project.git", + "ssh://git@github.xyz.com/org/project.git", + }, + { + "git@github.xyz.com:org/project.git?ref=test-branch", + "ssh://git@github.xyz.com/org/project.git?ref=test-branch", + }, + { + "git@github.xyz.com:org/project.git//module/a", + "ssh://git@github.xyz.com/org/project.git//module/a", + }, + { + "git@github.xyz.com:org/project.git//module/a?ref=test-branch", + "ssh://git@github.xyz.com/org/project.git//module/a?ref=test-branch", + }, + { + "git::ssh://git@git.example.com:2222/hashicorp/foo.git", + "ssh://git@git.example.com:2222/hashicorp/foo.git", + }, + } + + pwd := "/pwd" + for _, tc := range cases { + t.Run(tc.Input, func(t *testing.T) { + req := &Request{ + Src: tc.Input, + Pwd: pwd, + } + _, err := Detect(req, new(GitGetter)) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if req.Src != tc.Output { + t.Errorf("wrong result\ninput: %s\ngot: %s\nwant: %s", tc.Input, req.Src, tc.Output) + } + }) + } +} + // gitRepo is a helper struct which controls a single temp git repo. type gitRepo struct { t *testing.T From caf16e84dc2b22e814a319e9511d0317c716c80e Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 11 May 2020 15:05:59 +0200 Subject: [PATCH 094/109] improve error handling --- client.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/client.go b/client.go index b2f949b05..9d6ffcdf5 100644 --- a/client.go +++ b/client.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "os" "path/filepath" - "reflect" "strconv" "strings" @@ -47,7 +46,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { req.Mode = ModeAny } - var multierr *multierror.Error + var multierr []error for _, g := range c.Getters { ok, err := Detect(req, g) if err != nil { @@ -56,7 +55,6 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { if !ok { continue } - getterName := reflect.Indirect(reflect.ValueOf(g)).Type().Name() // If there is a subdir component, then we download the root separately // and then copy over the proper subdir. @@ -148,8 +146,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // Ask the getter which client mode to use req.Mode, err = g.Mode(ctx, req.u) if err != nil { - err = fmt.Errorf("%s failed: %s", getterName, err.Error()) - multierr = multierror.Append(multierr, err) + multierr = append(multierr, err) continue } @@ -183,8 +180,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { } if getFile { if err := g.GetFile(ctx, req); err != nil { - err = fmt.Errorf("%s failed: %s", getterName, err.Error()) - multierr = multierror.Append(multierr, err) + multierr = append(multierr, err) continue } @@ -235,8 +231,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // We're downloading a directory, which might require a bit more work // if we're specifying a subdir. if err := g.Get(ctx, req); err != nil { - err = fmt.Errorf("%s failed: %s", getterName, err.Error()) - multierr = multierror.Append(multierr, err) + multierr = append(multierr, err) continue } } @@ -261,8 +256,14 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { return &GetResult{req.Dst}, nil } + if len(multierr) == 1 { + return nil, multierr[0] + } + if multierr != nil { - return nil, fmt.Errorf("error downloading '%s': %s", req.Src, multierr.Error()) + var result *multierror.Error + result = multierror.Append(result, multierr...) + return nil, fmt.Errorf("error downloading '%s': %s", req.Src, result.Error()) } return nil, fmt.Errorf("error downloading '%s'", req.Src) From 39a5cf73877f9e00724835652b106d94854dbc15 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 11 May 2020 15:12:56 +0200 Subject: [PATCH 095/109] fix main.go --- cmd/go-getter/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/go-getter/main.go b/cmd/go-getter/main.go index 7ac583188..0f4569d5a 100644 --- a/cmd/go-getter/main.go +++ b/cmd/go-getter/main.go @@ -61,8 +61,8 @@ func main() { client := getter.DefaultClient getters := getter.Getters - getters["gcs"] = new(gcs.Getter) - getters["s3"] = new(s3.Getter) + getters = append(getters, new(gcs.Getter)) + getters = append(getters, new(s3.Getter)) client.Getters = getters errChan := make(chan error, 2) From 1636ace32f3f420fd490cbe10392c783ea373eaf Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 11 May 2020 15:56:07 +0200 Subject: [PATCH 096/109] logs to test windows tests on circleci --- client.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client.go b/client.go index 9d6ffcdf5..450f60aab 100644 --- a/client.go +++ b/client.go @@ -5,8 +5,10 @@ import ( "fmt" "github.com/hashicorp/go-multierror" "io/ioutil" + "log" "os" "path/filepath" + "reflect" "strconv" "strings" @@ -49,12 +51,15 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { var multierr []error for _, g := range c.Getters { ok, err := Detect(req, g) + getterName := reflect.Indirect(reflect.ValueOf(g)).Type().Name() if err != nil { return nil, err } if !ok { + log.Printf("%s MOSS NOT OK for %s", getterName, req.Src) continue } + log.Printf("%s MOSS OK for %s", getterName, req.Src) // If there is a subdir component, then we download the root separately // and then copy over the proper subdir. @@ -234,6 +239,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { multierr = append(multierr, err) continue } + log.Printf("MOSS after getting dir") } // If we have a subdir, copy that over From 9e480d648a95fd1b98fc3f1246243acaacb67ecb Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 11 May 2020 16:33:21 +0200 Subject: [PATCH 097/109] make filegetter accept win c:/ filepaths --- client.go | 6 ------ get.go | 2 +- get_file.go | 8 ++++++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/client.go b/client.go index 450f60aab..9d6ffcdf5 100644 --- a/client.go +++ b/client.go @@ -5,10 +5,8 @@ import ( "fmt" "github.com/hashicorp/go-multierror" "io/ioutil" - "log" "os" "path/filepath" - "reflect" "strconv" "strings" @@ -51,15 +49,12 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { var multierr []error for _, g := range c.Getters { ok, err := Detect(req, g) - getterName := reflect.Indirect(reflect.ValueOf(g)).Type().Name() if err != nil { return nil, err } if !ok { - log.Printf("%s MOSS NOT OK for %s", getterName, req.Src) continue } - log.Printf("%s MOSS OK for %s", getterName, req.Src) // If there is a subdir component, then we download the root separately // and then copy over the proper subdir. @@ -239,7 +234,6 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { multierr = append(multierr, err) continue } - log.Printf("MOSS after getting dir") } // If we have a subdir, copy that over diff --git a/get.go b/get.go index deb1af5c4..5e4160525 100644 --- a/get.go +++ b/get.go @@ -72,10 +72,10 @@ func init() { Getters = []Getter{ new(GitGetter), new(HgGetter), - new(FileGetter), new(SmbClientGetter), new(SmbMountGetter), httpGetter, + new(FileGetter), } } diff --git a/get_file.go b/get_file.go index 88b30b716..4f7947360 100644 --- a/get_file.go +++ b/get_file.go @@ -177,8 +177,12 @@ func (g *FileGetter) Detect(req *Request) (string, bool, error) { if g.validScheme(u.Scheme) { return src, true, nil } - // Valid url with a scheme that is not valid for current getter - return "", false, nil + if !(runtime.GOOS == "windows" && len(u.Scheme) == 1) { + return src, false, nil + } + // For windows, we try to get the artifact + // if it has a non valid scheme with 1 char + // e.g. C:/foo/bar for other cases a prefix file:// is necessary } if !filepath.IsAbs(src) { From e9fff3316e4247195645a59ac1fa621b265af491 Mon Sep 17 00:00:00 2001 From: Moss Date: Mon, 11 May 2020 17:39:00 +0200 Subject: [PATCH 098/109] override request directly --- detect.go | 5 ++--- detect_file_test.go | 4 ++-- gcs/get_gcs.go | 28 ++++++++++------------------ get.go | 2 +- get_file.go | 23 ++++++++++++----------- get_git.go | 34 +++++++++++++++++++--------------- get_hg.go | 22 ++++++++++++---------- get_http.go | 29 +++++++++-------------------- get_mock.go | 4 ++-- get_smb_mount.go | 29 +++++++++-------------------- get_smbclient.go | 29 +++++++++-------------------- s3/get_s3.go | 28 ++++++++++------------------ 12 files changed, 97 insertions(+), 140 deletions(-) diff --git a/detect.go b/detect.go index 4a5e4b97e..01fcf3bf7 100644 --- a/detect.go +++ b/detect.go @@ -19,9 +19,8 @@ func Detect(req *Request, getter Getter) (bool, error) { getSrc, subDir := SourceDirSubdir(getSrc) req.Src = getSrc - result, ok, err := getter.Detect(req) + ok, err := getter.Detect(req) if err != nil { - req.Src = result return true, err } if !ok { @@ -30,7 +29,7 @@ func Detect(req *Request, getter Getter) (bool, error) { return ok, nil } - result, detectSubdir := SourceDirSubdir(result) + result, detectSubdir := SourceDirSubdir(req.Src) // If we have a subdir from the detection, then prepend it to our // requested subdir. diff --git a/detect_file_test.go b/detect_file_test.go index 32f65ef85..b4a8d59aa 100644 --- a/detect_file_test.go +++ b/detect_file_test.go @@ -81,8 +81,8 @@ func TestFileDetector(t *testing.T) { } var noPwdFileTests = []fileTest{ - {in: "./foo", pwd: "", out: "", err: true}, - {in: "foo", pwd: "", out: "", err: true}, + {in: "./foo", pwd: "", out: "./foo", err: true}, + {in: "foo", pwd: "", out: "foo", err: true}, } var noPwdUnixFileTests = []fileTest{ diff --git a/gcs/get_gcs.go b/gcs/get_gcs.go index 88d997b0d..8ee68fbd0 100644 --- a/gcs/get_gcs.go +++ b/gcs/get_gcs.go @@ -166,10 +166,10 @@ func (g *Getter) parseURL(u *url.URL) (bucket, path string, err error) { return } -func (g *Getter) Detect(req *getter.Request) (string, bool, error) { +func (g *Getter) Detect(req *getter.Request) (bool, error) { src := req.Src if len(src) == 0 { - return "", false, nil + return false, nil } if req.Forced != "" { @@ -177,7 +177,7 @@ func (g *Getter) Detect(req *getter.Request) (string, bool, error) { if !g.validScheme(req.Forced) { // Current getter is not the forced one // Don't use it to try to download the artifact - return "", false, nil + return false, nil } } isForcedGetter := req.Forced != "" && g.validScheme(req.Forced) @@ -186,30 +186,22 @@ func (g *Getter) Detect(req *getter.Request) (string, bool, error) { if err == nil && u.Scheme != "" { if isForcedGetter { // Is the forced getter and source is a valid url - return src, true, nil + return true, nil } if g.validScheme(u.Scheme) { - return src, true, nil + return true, nil } // Valid url with a scheme that is not valid for current getter - return "", false, nil + return false, nil } if strings.Contains(src, "googleapis.com/") { - return g.detectHTTP(src) + src, ok, err := g.detectHTTP(src) + req.Src = src + return ok, err } - if isForcedGetter { - // Is the forced getter and should be used to download the artifact - if req.Pwd != "" && !filepath.IsAbs(src) { - // Make sure to add pwd to relative paths - src = filepath.Join(req.Pwd, src) - } - // Make sure we're using "/" on Windows. URLs are "/"-based. - return filepath.ToSlash(src), true, nil - } - - return "", false, nil + return false, nil } func (g *Getter) detectHTTP(src string) (string, bool, error) { diff --git a/get.go b/get.go index 5e4160525..424d7c717 100644 --- a/get.go +++ b/get.go @@ -45,7 +45,7 @@ type Getter interface { // Detect will detect whether the string matches a known pattern to // turn it into a proper URL. - Detect(*Request) (string, bool, error) + Detect(*Request) (bool, error) } // Getters is the mapping of scheme to the Getter implementation that will diff --git a/get_file.go b/get_file.go index 4f7947360..ba096efac 100644 --- a/get_file.go +++ b/get_file.go @@ -150,12 +150,12 @@ func (g *FileGetter) GetFile(ctx context.Context, req *Request) error { return err } -func (g *FileGetter) Detect(req *Request) (string, bool, error) { +func (g *FileGetter) Detect(req *Request) (bool, error) { var src, pwd string src = req.Src pwd = req.Pwd if len(src) == 0 { - return "", false, nil + return false, nil } if req.Forced != "" { @@ -163,7 +163,7 @@ func (g *FileGetter) Detect(req *Request) (string, bool, error) { if !g.validScheme(req.Forced) { // Current getter is not the Forced one // Don't use it to try to download the artifact - return "", false, nil + return false, nil } } isForcedGetter := req.Forced != "" && g.validScheme(req.Forced) @@ -172,13 +172,13 @@ func (g *FileGetter) Detect(req *Request) (string, bool, error) { if err == nil && u.Scheme != "" { if isForcedGetter { // Is the Forced getter and source is a valid url - return src, true, nil + return true, nil } if g.validScheme(u.Scheme) { - return src, true, nil + return true, nil } if !(runtime.GOOS == "windows" && len(u.Scheme) == 1) { - return src, false, nil + return false, nil } // For windows, we try to get the artifact // if it has a non valid scheme with 1 char @@ -187,7 +187,7 @@ func (g *FileGetter) Detect(req *Request) (string, bool, error) { if !filepath.IsAbs(src) { if pwd == "" { - return "", true, fmt.Errorf( + return true, fmt.Errorf( "relative paths require a module with a pwd") } @@ -199,19 +199,19 @@ func (g *FileGetter) Detect(req *Request) (string, bool, error) { // caught later when we try to use the URL. if fi, err := os.Lstat(pwd); !os.IsNotExist(err) { if err != nil { - return "", true, err + return true, err } if fi.Mode()&os.ModeSymlink != 0 { pwd, err = filepath.EvalSymlinks(pwd) if err != nil { - return "", true, err + return true, err } // The symlink itself might be a relative path, so we have to // resolve this to have a correctly rooted URL. pwd, err = filepath.Abs(pwd) if err != nil { - return "", true, err + return true, err } } } @@ -219,7 +219,8 @@ func (g *FileGetter) Detect(req *Request) (string, bool, error) { src = filepath.Join(pwd, src) } - return fmtFileURL(src), true, nil + req.Src = fmtFileURL(src) + return true, nil } func (g *FileGetter) validScheme(scheme string) bool { diff --git a/get_git.go b/get_git.go index 13177709b..d30abfde2 100644 --- a/get_git.go +++ b/get_git.go @@ -316,10 +316,10 @@ func checkGitVersion(min string) error { return nil } -func (g *GitGetter) Detect(req *Request) (string, bool, error) { +func (g *GitGetter) Detect(req *Request) (bool, error) { src := req.Src if len(src) == 0 { - return "", false, nil + return false, nil } if req.Forced != "" { @@ -327,7 +327,7 @@ func (g *GitGetter) Detect(req *Request) (string, bool, error) { if !g.validScheme(req.Forced) { // Current getter is not the Forced one // Don't use it to try to download the artifact - return "", false, nil + return false, nil } } isForcedGetter := req.Forced != "" && g.validScheme(req.Forced) @@ -336,47 +336,50 @@ func (g *GitGetter) Detect(req *Request) (string, bool, error) { if err == nil && u.Scheme != "" { if isForcedGetter { // Is the Forced getter and source is a valid url - return src, true, nil + return true, nil } if g.validScheme(u.Scheme) { - return src, true, nil + return true, nil } // Valid url with a scheme that is not valid for current getter - return "", false, nil + return false, nil } u, err = detectSSH(src) if err != nil { - return "", true, err + return true, err } if u != nil { // We require the username to be "git" to assume that this is a Git URL if u.User.Username() == "git" { - return u.String(), true, nil + req.Src = u.String() + return true, nil } } result, ok, err := detectGitHub(src) if err != nil { - return "", ok, err + return ok, err } if ok { - return result, ok, nil + req.Src = result + return ok, nil } result, u, err = detectBitBucket(src) if err != nil { - return "", true, err + return true, err } if result == "git" { if !strings.HasSuffix(u.Path, ".git") { u.Path += ".git" } - return u.String(), true, nil + req.Src = u.String() + return true, nil } if _, err = url.Parse(req.Src); err != nil { - return "", true, nil + return true, nil } if isForcedGetter { @@ -386,10 +389,11 @@ func (g *GitGetter) Detect(req *Request) (string, bool, error) { src = filepath.Join(req.Pwd, src) } // Make sure we're using "/" on Windows. URLs are "/"-based. - return filepath.ToSlash(src), true, nil + req.Src = filepath.ToSlash(src) + return true, nil } - return "", false, nil + return false, nil } func detectBitBucket(src string) (string, *url.URL, error) { diff --git a/get_hg.go b/get_hg.go index dc5fb371d..8929a1c43 100644 --- a/get_hg.go +++ b/get_hg.go @@ -125,10 +125,10 @@ func (g *HgGetter) update(ctx context.Context, dst string, u *url.URL, rev strin return getRunCommand(cmd) } -func (g *HgGetter) Detect(req *Request) (string, bool, error) { +func (g *HgGetter) Detect(req *Request) (bool, error) { src := req.Src if len(src) == 0 { - return "", false, nil + return false, nil } if req.Forced != "" { @@ -136,7 +136,7 @@ func (g *HgGetter) Detect(req *Request) (string, bool, error) { if !g.validScheme(req.Forced) { // Current getter is not the Forced one // Don't use it to try to download the artifact - return "", false, nil + return false, nil } } isForcedGetter := req.Forced != "" && g.validScheme(req.Forced) @@ -145,21 +145,22 @@ func (g *HgGetter) Detect(req *Request) (string, bool, error) { if err == nil && u.Scheme != "" { if isForcedGetter { // Is the Forced getter and source is a valid url - return src, true, nil + return true, nil } if g.validScheme(u.Scheme) { - return src, true, nil + return true, nil } // Valid url with a scheme that is not valid for current getter - return "", false, nil + return false, nil } result, u, err := detectBitBucket(src) if err != nil { - return "", true, err + return true, err } if result == "hg" { - return u.String(), true, nil + req.Src = u.String() + return true, nil } if isForcedGetter { @@ -169,10 +170,11 @@ func (g *HgGetter) Detect(req *Request) (string, bool, error) { src = filepath.Join(req.Pwd, src) } // Make sure we're using "/" on Windows. URLs are "/"-based. - return filepath.ToSlash(src), true, nil + req.Src = filepath.ToSlash(src) + return true, nil } - return "", false, nil + return false, nil } func (g *HgGetter) validScheme(scheme string) bool { diff --git a/get_http.go b/get_http.go index 43b13320f..f4dbfd346 100644 --- a/get_http.go +++ b/get_http.go @@ -320,10 +320,9 @@ func charsetReader(charset string, input io.Reader) (io.Reader, error) { } } -func (g *HttpGetter) Detect(req *Request) (string, bool, error) { - src := req.Src - if len(src) == 0 { - return "", false, nil +func (g *HttpGetter) Detect(req *Request) (bool, error) { + if len(req.Src) == 0 { + return false, nil } if req.Forced != "" { @@ -331,35 +330,25 @@ func (g *HttpGetter) Detect(req *Request) (string, bool, error) { if !g.validScheme(req.Forced) { // Current getter is not the Forced one // Don't use it to try to download the artifact - return "", false, nil + return false, nil } } isForcedGetter := req.Forced != "" && g.validScheme(req.Forced) - u, err := url.Parse(src) + u, err := url.Parse(req.Src) if err == nil && u.Scheme != "" { if isForcedGetter { // Is the Forced getter and source is a valid url - return src, true, nil + return true, nil } if g.validScheme(u.Scheme) { - return src, true, nil + return true, nil } // Valid url with a scheme that is not valid for current getter - return "", false, nil + return false, nil } - if isForcedGetter { - // Is the Forced getter and should be used to download the artifact - if req.Pwd != "" && !filepath.IsAbs(src) { - // Make sure to add pwd to relative paths - src = filepath.Join(req.Pwd, src) - } - // Make sure we're using "/" on Windows. URLs are "/"-based. - return filepath.ToSlash(src), true, nil - } - - return "", false, nil + return false, nil } func (g *HttpGetter) validScheme(scheme string) bool { diff --git a/get_mock.go b/get_mock.go index 491b211c1..a0aa1050d 100644 --- a/get_mock.go +++ b/get_mock.go @@ -53,9 +53,9 @@ func (g *MockGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { return ModeFile, nil } -func (g *MockGetter) Detect(req *Request) (string, bool, error) { +func (g *MockGetter) Detect(req *Request) (bool, error) { if g.Proxy != nil { return g.Proxy.Detect(req) } - return req.Src, true, nil + return true, nil } diff --git a/get_smb_mount.go b/get_smb_mount.go index b6b21dbb9..6d1050bab 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -80,10 +80,9 @@ func (g *SmbMountGetter) findShare(u *url.URL) string { return "." } -func (g *SmbMountGetter) Detect(req *Request) (string, bool, error) { - src := req.Src - if len(src) == 0 { - return "", false, nil +func (g *SmbMountGetter) Detect(req *Request) (bool, error) { + if len(req.Src) == 0 { + return false, nil } if req.Forced != "" { @@ -91,35 +90,25 @@ func (g *SmbMountGetter) Detect(req *Request) (string, bool, error) { if !g.validScheme(req.Forced) { // Current getter is not the Forced one // Don't use it to try to download the artifact - return "", false, nil + return false, nil } } isForcedGetter := req.Forced != "" && g.validScheme(req.Forced) - u, err := url.Parse(src) + u, err := url.Parse(req.Src) if err == nil && u.Scheme != "" { if isForcedGetter { // Is the Forced getter and source is a valid url - return src, true, nil + return true, nil } if g.validScheme(u.Scheme) { - return src, true, nil + return true, nil } // Valid url with a scheme that is not valid for current getter - return "", false, nil + return false, nil } - if isForcedGetter { - // Is the Forced getter and should be used to download the artifact - if req.Pwd != "" && !filepath.IsAbs(src) { - // Make sure to add pwd to relative paths - src = filepath.Join(req.Pwd, src) - } - // Make sure we're using "/" on Windows. URLs are "/"-based. - return filepath.ToSlash(src), true, nil - } - - return "", false, nil + return false, nil } func (g *SmbMountGetter) validScheme(scheme string) bool { diff --git a/get_smbclient.go b/get_smbclient.go index cc494b10c..02d0dbcbd 100644 --- a/get_smbclient.go +++ b/get_smbclient.go @@ -282,10 +282,9 @@ func (g *SmbClientGetter) runSmbClientCommand(dst string, args []string) (string return buf.String(), fmt.Errorf("error running %s: %s", cmd.Path, buf.String()) } -func (g *SmbClientGetter) Detect(req *Request) (string, bool, error) { - src := req.Src - if len(src) == 0 { - return "", false, nil +func (g *SmbClientGetter) Detect(req *Request) (bool, error) { + if len(req.Src) == 0 { + return false, nil } if req.Forced != "" { @@ -293,35 +292,25 @@ func (g *SmbClientGetter) Detect(req *Request) (string, bool, error) { if !g.validScheme(req.Forced) { // Current getter is not the Forced one // Don't use it to try to download the artifact - return "", false, nil + return false, nil } } isForcedGetter := req.Forced != "" && g.validScheme(req.Forced) - u, err := url.Parse(src) + u, err := url.Parse(req.Src) if err == nil && u.Scheme != "" { if isForcedGetter { // Is the Forced getter and source is a valid url - return src, true, nil + return true, nil } if g.validScheme(u.Scheme) { - return src, true, nil + return true, nil } // Valid url with a scheme that is not valid for current getter - return "", false, nil + return false, nil } - if isForcedGetter { - // Is the Forced getter and should be used to download the artifact - if req.Pwd != "" && !filepath.IsAbs(src) { - // Make sure to add pwd to relative paths - src = filepath.Join(req.Pwd, src) - } - // Make sure we're using "/" on Windows. URLs are "/"-based. - return filepath.ToSlash(src), true, nil - } - - return "", false, nil + return false, nil } func (g *SmbClientGetter) validScheme(scheme string) bool { diff --git a/s3/get_s3.go b/s3/get_s3.go index ba5936cbe..70d9459af 100644 --- a/s3/get_s3.go +++ b/s3/get_s3.go @@ -273,10 +273,10 @@ func (g *Getter) parseUrl(u *url.URL) (region, bucket, path, version string, cre return } -func (g *Getter) Detect(req *getter.Request) (string, bool, error) { +func (g *Getter) Detect(req *getter.Request) (bool, error) { src := req.Src if len(src) == 0 { - return "", false, nil + return false, nil } if req.Forced != "" { @@ -284,7 +284,7 @@ func (g *Getter) Detect(req *getter.Request) (string, bool, error) { if !g.validScheme(req.Forced) { // Current getter is not the forced one // Don't use it to try to download the artifact - return "", false, nil + return false, nil } } isForcedGetter := req.Forced != "" && g.validScheme(req.Forced) @@ -293,30 +293,22 @@ func (g *Getter) Detect(req *getter.Request) (string, bool, error) { if err == nil && u.Scheme != "" { if isForcedGetter { // Is the forced getter and source is a valid url - return src, true, nil + return true, nil } if g.validScheme(u.Scheme) { - return src, true, nil + return true, nil } // Valid url with a scheme that is not valid for current getter - return "", false, nil + return false, nil } if strings.Contains(src, ".amazonaws.com/") { - return g.detectHTTP(src) + src, ok, err := g.detectHTTP(src) + req.Src = src + return ok, err } - if isForcedGetter { - // Is the forced getter and should be used to download the artifact - if req.Pwd != "" && !filepath.IsAbs(src) { - // Make sure to add pwd to relative paths - src = filepath.Join(req.Pwd, src) - } - // Make sure we're using "/" on Windows. URLs are "/"-based. - return filepath.ToSlash(src), true, nil - } - - return "", false, nil + return false, nil } func (g *Getter) detectHTTP(src string) (string, bool, error) { From 93a026d8ecabe8d8f55648f7053a2ed7799b87c2 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 12 May 2020 16:18:11 +0200 Subject: [PATCH 099/109] Move some tests and make the code look better' --- client.go | 41 +++--- detect.go | 18 +-- detect_bitbucket_test.go | 73 ----------- detect_file_test.go | 122 ------------------ detect_test.go | 18 +-- get_file_test.go | 116 +++++++++++++++++ ...file_unix_test.go => get_file_unix_test.go | 10 +- get_git_test.go | 8 ++ get_hg_test.go | 55 ++++++++ 9 files changed, 214 insertions(+), 247 deletions(-) delete mode 100644 detect_bitbucket_test.go delete mode 100644 detect_file_test.go rename detect_file_unix_test.go => get_file_unix_test.go (85%) diff --git a/client.go b/client.go index 9d6ffcdf5..9ade5b997 100644 --- a/client.go +++ b/client.go @@ -46,6 +46,21 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { req.Mode = ModeAny } + // If there is a subdir component, then we download the root separately + // and then copy over the proper subdir. + var realDst, subDir string + req.Src, subDir = SourceDirSubdir(req.Src) + if subDir != "" { + td, tdcloser, err := safetemp.Dir("", "getter") + if err != nil { + return nil, err + } + defer tdcloser.Close() + + realDst = req.Dst + req.Dst = td + } + var multierr []error for _, g := range c.Getters { ok, err := Detect(req, g) @@ -56,23 +71,8 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { continue } - // If there is a subdir component, then we download the root separately - // and then copy over the proper subdir. - var realDst, subDir string - req.Src, subDir = SourceDirSubdir(req.Src) - if subDir != "" { - td, tdcloser, err := safetemp.Dir("", "getter") - if err != nil { - return nil, err - } - // TODO @sylviamoss defer doesn't work here inside a for loop - defer tdcloser.Close() - - realDst = req.Dst - req.Dst = td - } - - req.u, err = urlhelper.Parse(req.Src) + u, err := urlhelper.Parse(req.Src) + req.u = u if err != nil { return nil, err } @@ -107,7 +107,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // If we have a decompressor, then we need to change the destination // to download to a temporary path. We unarchive this into the final, // real path. - var decompressDst string + var decompressDst, td string var decompressDir bool decompressor := c.Decompressors[archiveV] if decompressor != nil { @@ -118,7 +118,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { return nil, fmt.Errorf( "Error creating temporary directory for archive: %s", err) } - // TODO @sylviamoss defer doesn't work here inside a for loop + // If success clean this after loop defer os.RemoveAll(td) // Swap the download directory to be our temporary path and @@ -146,6 +146,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // Ask the getter which client mode to use req.Mode, err = g.Mode(ctx, req.u) if err != nil { + os.RemoveAll(td) multierr = append(multierr, err) continue } @@ -180,6 +181,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { } if getFile { if err := g.GetFile(ctx, req); err != nil { + os.RemoveAll(td) multierr = append(multierr, err) continue } @@ -231,6 +233,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // We're downloading a directory, which might require a bit more work // if we're specifying a subdir. if err := g.Get(ctx, req); err != nil { + os.RemoveAll(td) multierr = append(multierr, err) continue } diff --git a/detect.go b/detect.go index 01fcf3bf7..4486acc47 100644 --- a/detect.go +++ b/detect.go @@ -3,7 +3,6 @@ package getter import ( "fmt" "github.com/hashicorp/go-getter/helper/url" - "path/filepath" ) // Detect is a method used to detect if a Getter is a candidate for downloading an artifact @@ -13,10 +12,9 @@ func Detect(req *Request, getter Getter) (bool, error) { originalSrc := req.Src getForce, getSrc := getForcedGetter(req.Src) - req.Forced = getForce - - // Separate out the subdir if there is one, we don't pass that to detect - getSrc, subDir := SourceDirSubdir(getSrc) + if req.Forced == "" { + req.Forced = getForce + } req.Src = getSrc ok, err := getter.Detect(req) @@ -34,19 +32,11 @@ func Detect(req *Request, getter Getter) (bool, error) { // If we have a subdir from the detection, then prepend it to our // requested subdir. if detectSubdir != "" { - if subDir != "" { - subDir = filepath.Join(detectSubdir, subDir) - } else { - subDir = detectSubdir - } - } - - if subDir != "" { u, err := url.Parse(result) if err != nil { return true, fmt.Errorf("Error parsing URL: %s", err) } - u.Path += "//" + subDir + u.Path += "//" + detectSubdir // a subdir may contain wildcards, but in order to support them we // have to ensure the path isn't escaped. diff --git a/detect_bitbucket_test.go b/detect_bitbucket_test.go deleted file mode 100644 index dc3c3d1ed..000000000 --- a/detect_bitbucket_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package getter - -import ( - "net/http" - "strings" - "testing" -) - -const testBBUrl = "https://bitbucket.org/hashicorp/tf-test-git" - -func TestBitBucketDetector(t *testing.T) { - t.Parallel() - - if _, err := http.Get(testBBUrl); err != nil { - t.Log("internet may not be working, skipping BB tests") - t.Skip() - } - - cases := []struct { - Input string - Output string - g Getter - }{ - // HTTP - { - "bitbucket.org/hashicorp/tf-test-git", - "https://bitbucket.org/hashicorp/tf-test-git.git", - new(GitGetter), - }, - { - "bitbucket.org/hashicorp/tf-test-git.git", - "https://bitbucket.org/hashicorp/tf-test-git.git", - new(GitGetter), - }, - { - "bitbucket.org/hashicorp/tf-test-hg", - "https://bitbucket.org/hashicorp/tf-test-hg", - new(HgGetter), - }, - } - - pwd := "/pwd" - for i, tc := range cases { - var err error - for i := 0; i < 3; i++ { - var ok bool - req := &Request{ - Src: tc.Input, - Pwd: pwd, - } - ok, err = Detect(req, tc.g) - if err != nil { - if strings.Contains(err.Error(), "invalid character") { - continue - } - - t.Fatalf("err: %s", err) - } - if !ok { - t.Fatal("not ok") - } - - if req.Src != tc.Output { - t.Fatalf("%d: bad: %#v", i, req.Src) - } - - break - } - if i >= 3 { - t.Fatalf("failure from bitbucket: %s", err) - } - } -} diff --git a/detect_file_test.go b/detect_file_test.go deleted file mode 100644 index b4a8d59aa..000000000 --- a/detect_file_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package getter - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "testing" -) - -type fileTest struct { - in, pwd, out string - symlink, err bool -} - -var fileTests = []fileTest{ - {"./foo", "/pwd", "/pwd/foo", false, false}, - {"./foo?foo=bar", "/pwd", "/pwd/foo?foo=bar", false, false}, - {"foo", "/pwd", "/pwd/foo", false, false}, -} - -var unixFileTests = []fileTest{ - {"./foo", "testdata/detect-file-symlink-pwd/syml/pwd", - "testdata/detect-file-symlink-pwd/real/foo", true, false}, - - {"/foo", "/pwd", "/foo", false, false}, - {"/foo?bar=baz", "/pwd", "/foo?bar=baz", false, false}, -} - -var winFileTests = []fileTest{ - {"/foo", "/pwd", "/pwd/foo", false, false}, - {`C:\`, `/pwd`, `C:/`, false, false}, - {`C:\?bar=baz`, `/pwd`, `C:/?bar=baz`, false, false}, -} - -func TestFileDetector(t *testing.T) { - if runtime.GOOS == "windows" { - fileTests = append(fileTests, winFileTests...) - } else { - fileTests = append(fileTests, unixFileTests...) - } - - // Get the pwd - pwdRoot, err := os.Getwd() - if err != nil { - t.Fatalf("err: %s", err) - } - pwdRoot, err = filepath.Abs(pwdRoot) - if err != nil { - t.Fatalf("err: %s", err) - } - - f := new(FileGetter) - for i, tc := range fileTests { - t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - pwd := tc.pwd - - req := &Request{ - Src: tc.in, - Pwd: pwd, - } - ok, err := Detect(req, f) - if err != nil { - t.Fatalf("err: %s", err) - } - if !ok { - t.Fatal("not ok") - } - - expected := tc.out - if tc.symlink { - expected = filepath.Join(pwdRoot, expected) - } - - if req.Src != expected { - t.Fatalf("input: %q\npwd: %q\nexpected: %q\nbad output: %#v", - tc.in, pwd, expected, req.Src) - } - }) - } -} - -var noPwdFileTests = []fileTest{ - {in: "./foo", pwd: "", out: "./foo", err: true}, - {in: "foo", pwd: "", out: "foo", err: true}, -} - -var noPwdUnixFileTests = []fileTest{ - {in: "/foo", pwd: "", out: "/foo", err: false}, -} - -var noPwdWinFileTests = []fileTest{ - {in: "/foo", pwd: "", out: "", err: true}, - {in: `C:\`, pwd: ``, out: `C:/`, err: false}, -} - -func TestFileDetector_noPwd(t *testing.T) { - if runtime.GOOS == "windows" { - noPwdFileTests = append(noPwdFileTests, noPwdWinFileTests...) - } else { - noPwdFileTests = append(noPwdFileTests, noPwdUnixFileTests...) - } - - f := new(FileGetter) - for i, tc := range noPwdFileTests { - req := &Request{ - Src: tc.in, - Pwd: tc.pwd, - } - ok, err := Detect(req, f) - if err != nil != tc.err { - t.Fatalf("%d: err: %s", i, err) - } - if !ok { - t.Fatal("not ok") - } - - if req.Src != tc.out { - t.Fatalf("%d: bad: %#v", i, req.Src) - } - } -} diff --git a/detect_test.go b/detect_test.go index 5d7bc911c..c69fb6ae8 100644 --- a/detect_test.go +++ b/detect_test.go @@ -23,19 +23,12 @@ func TestDetect(t *testing.T) { new(GitGetter), }, { - "./foo//bar", + "./foo", "/foo", - "/foo/foo//bar", + "/foo/foo", false, new(FileGetter), }, - { - "git::github.com/hashicorp/foo//bar", - "", - "https://github.com/hashicorp/foo.git//bar", - false, - new(GitGetter), - }, { "git::https://github.com/hashicorp/consul.git", "", @@ -57,13 +50,6 @@ func TestDetect(t *testing.T) { false, new(GitGetter), }, - { - "./foo/archive//*", - "/bar", - "/bar/foo/archive//*", - false, - new(FileGetter), - }, // https://github.com/hashicorp/go-getter/pull/124 { diff --git a/get_file_test.go b/get_file_test.go index 71e05f7c7..3c5e2e6dc 100644 --- a/get_file_test.go +++ b/get_file_test.go @@ -2,8 +2,10 @@ package getter import ( "context" + "fmt" "os" "path/filepath" + "runtime" "testing" testing_helper "github.com/hashicorp/go-getter/v2/helper/testing" @@ -257,3 +259,117 @@ func TestFileGetter_Mode_dir(t *testing.T) { t.Fatal("expect ModeDir") } } + +type fileTest struct { + in, pwd, out string + symlink, err bool +} + +var fileTests = []fileTest{ + {"./foo", "/pwd", "/pwd/foo", false, false}, + {"./foo?foo=bar", "/pwd", "/pwd/foo?foo=bar", false, false}, + {"foo", "/pwd", "/pwd/foo", false, false}, +} + +var unixFileTests = []fileTest{ + {"./foo", "testdata/detect-file-symlink-pwd/syml/pwd", + "testdata/detect-file-symlink-pwd/real/foo", true, false}, + + {"/foo", "/pwd", "/foo", false, false}, + {"/foo?bar=baz", "/pwd", "/foo?bar=baz", false, false}, +} + +var winFileTests = []fileTest{ + {"/foo", "/pwd", "/pwd/foo", false, false}, + {`C:\`, `/pwd`, `C:/`, false, false}, + {`C:\?bar=baz`, `/pwd`, `C:/?bar=baz`, false, false}, +} + +func TestFileDetector(t *testing.T) { + if runtime.GOOS == "windows" { + fileTests = append(fileTests, winFileTests...) + } else { + fileTests = append(fileTests, unixFileTests...) + } + + // Get the pwd + pwdRoot, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + pwdRoot, err = filepath.Abs(pwdRoot) + if err != nil { + t.Fatalf("err: %s", err) + } + + f := new(FileGetter) + for i, tc := range fileTests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + pwd := tc.pwd + + req := &Request{ + Src: tc.in, + Pwd: pwd, + } + ok, err := Detect(req, f) + if err != nil { + t.Fatalf("err: %s", err) + } + if !ok { + t.Fatal("not ok") + } + + expected := tc.out + if tc.symlink { + expected = filepath.Join(pwdRoot, expected) + } + + if req.Src != expected { + t.Fatalf("input: %q\npwd: %q\nexpected: %q\nbad output: %#v", + tc.in, pwd, expected, req.Src) + } + }) + } +} + +var noPwdFileTests = []fileTest{ + {in: "./foo", pwd: "", out: "./foo", err: true}, + {in: "foo", pwd: "", out: "foo", err: true}, +} + +var noPwdUnixFileTests = []fileTest{ + {in: "/foo", pwd: "", out: "/foo", err: false}, +} + +var noPwdWinFileTests = []fileTest{ + {in: "/foo", pwd: "", out: "", err: true}, + {in: `C:\`, pwd: ``, out: `C:/`, err: false}, +} + +func TestFileDetector_noPwd(t *testing.T) { + if runtime.GOOS == "windows" { + noPwdFileTests = append(noPwdFileTests, noPwdWinFileTests...) + } else { + noPwdFileTests = append(noPwdFileTests, noPwdUnixFileTests...) + } + + f := new(FileGetter) + for i, tc := range noPwdFileTests { + req := &Request{ + Src: tc.in, + Pwd: tc.pwd, + } + ok, err := Detect(req, f) + if err != nil != tc.err { + t.Fatalf("%d: err: %s", i, err) + } + if !ok { + t.Fatal("not ok") + } + + if req.Src != tc.out { + t.Fatalf("%d: bad: %#v", i, req.Src) + } + } +} + diff --git a/detect_file_unix_test.go b/get_file_unix_test.go similarity index 85% rename from detect_file_unix_test.go rename to get_file_unix_test.go index 0d3ebf6df..f99e199bf 100644 --- a/detect_file_unix_test.go +++ b/get_file_unix_test.go @@ -56,15 +56,19 @@ func TestFileDetector_relativeSymlink(t *testing.T) { // if detech doesn't fully resolve the pwd symlink, the output will be the // invalid path: "file:///../modules/foo" f := new(FileGetter) - out, ok, err := f.Detect("../modules/foo", "./linkedPWD") + req := &Request{ + Src: "../modules/foo", + Pwd: "./linkedPWD", + } + ok, err := f.Detect(req) if err != nil { t.Fatalf("err: %v", err) } if !ok { t.Fatal("not ok") } - if out != "file://"+filepath.Join(tmpDir, "modules/foo") { - t.Logf("expected: %v", "file://"+filepath.Join(tmpDir, "modules/foo")) + if req.Src != filepath.Join(tmpDir, "modules/foo") { + t.Logf("expected: %v", filepath.Join(tmpDir, "modules/foo")) t.Fatalf("bad: %v", out) } } diff --git a/get_git_test.go b/get_git_test.go index 8ea60b16b..5a9597034 100644 --- a/get_git_test.go +++ b/get_git_test.go @@ -678,6 +678,14 @@ func TestGitGetter_Detector(t *testing.T) { "git::ssh://git@git.example.com:2222/hashicorp/foo.git", "ssh://git@git.example.com:2222/hashicorp/foo.git", }, + { + "bitbucket.org/hashicorp/tf-test-git", + "https://bitbucket.org/hashicorp/tf-test-git.git", + }, + { + "bitbucket.org/hashicorp/tf-test-git.git", + "https://bitbucket.org/hashicorp/tf-test-git.git", + }, } pwd := "/pwd" diff --git a/get_hg_test.go b/get_hg_test.go index 41486bc30..b8f60ced3 100644 --- a/get_hg_test.go +++ b/get_hg_test.go @@ -2,9 +2,11 @@ package getter import ( "context" + "net/http" "os" "os/exec" "path/filepath" + "strings" "testing" testing_helper "github.com/hashicorp/go-getter/v2/helper/testing" @@ -118,3 +120,56 @@ func TestHgGetter_GetFile(t *testing.T) { } testing_helper.AssertContents(t, dst, "Hello\n") } + +const testBBUrl = "https://bitbucket.org/hashicorp/tf-test-git" + +func TestHgGetter_DetectBitBucketDetector(t *testing.T) { + t.Parallel() + + if _, err := http.Get(testBBUrl); err != nil { + t.Log("internet may not be working, skipping BB tests") + t.Skip() + } + + cases := []struct { + Input string + Output string + }{ + { + "bitbucket.org/hashicorp/tf-test-hg", + "https://bitbucket.org/hashicorp/tf-test-hg", + }, + } + + pwd := "/pwd" + for i, tc := range cases { + var err error + for i := 0; i < 3; i++ { + var ok bool + req := &Request{ + Src: tc.Input, + Pwd: pwd, + } + ok, err = Detect(req, new(HgGetter)) + if err != nil { + if strings.Contains(err.Error(), "invalid character") { + continue + } + + t.Fatalf("err: %s", err) + } + if !ok { + t.Fatal("not ok") + } + + if req.Src != tc.Output { + t.Fatalf("%d: bad: %#v", i, req.Src) + } + + break + } + if i >= 3 { + t.Fatalf("failure from bitbucket: %s", err) + } + } +} \ No newline at end of file From 12cc1d06b9df407cb19b5983620586aa6850f833 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 12 May 2020 17:08:35 +0200 Subject: [PATCH 100/109] Fix fmt --- client.go | 5 +++-- detect.go | 5 ++--- get_file_test.go | 1 - get_hg_test.go | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index 9ade5b997..107196f00 100644 --- a/client.go +++ b/client.go @@ -146,7 +146,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // Ask the getter which client mode to use req.Mode, err = g.Mode(ctx, req.u) if err != nil { - os.RemoveAll(td) + os.RemoveAll(td) // Removes temporary folder multierr = append(multierr, err) continue } @@ -233,7 +233,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // We're downloading a directory, which might require a bit more work // if we're specifying a subdir. if err := g.Get(ctx, req); err != nil { - os.RemoveAll(td) + os.RemoveAll(td) // Removes temporary folder multierr = append(multierr, err) continue } @@ -260,6 +260,7 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { } if len(multierr) == 1 { + // This is for keeping the error original format return nil, multierr[0] } diff --git a/detect.go b/detect.go index 4486acc47..3ac8a0384 100644 --- a/detect.go +++ b/detect.go @@ -6,13 +6,12 @@ import ( ) // Detect is a method used to detect if a Getter is a candidate for downloading an artifact -// by validating if a source string is detected to be of a known pattern, -// and transforming it to a known pattern when necessary. +// by calling the Getter.Detect(*Request) method func Detect(req *Request, getter Getter) (bool, error) { originalSrc := req.Src getForce, getSrc := getForcedGetter(req.Src) - if req.Forced == "" { + if getForce != "" { req.Forced = getForce } diff --git a/get_file_test.go b/get_file_test.go index 3c5e2e6dc..bb6bf73a1 100644 --- a/get_file_test.go +++ b/get_file_test.go @@ -372,4 +372,3 @@ func TestFileDetector_noPwd(t *testing.T) { } } } - diff --git a/get_hg_test.go b/get_hg_test.go index b8f60ced3..e207cfea8 100644 --- a/get_hg_test.go +++ b/get_hg_test.go @@ -172,4 +172,4 @@ func TestHgGetter_DetectBitBucketDetector(t *testing.T) { t.Fatalf("failure from bitbucket: %s", err) } } -} \ No newline at end of file +} From cee4ecd874985b7b264cdeff4ec91f74ec29eab3 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 12 May 2020 17:21:25 +0200 Subject: [PATCH 101/109] Fix windows test --- get_file_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get_file_test.go b/get_file_test.go index bb6bf73a1..e485c12a6 100644 --- a/get_file_test.go +++ b/get_file_test.go @@ -342,7 +342,7 @@ var noPwdUnixFileTests = []fileTest{ } var noPwdWinFileTests = []fileTest{ - {in: "/foo", pwd: "", out: "", err: true}, + {in: "/foo", pwd: "", out: "/foo", err: true}, {in: `C:\`, pwd: ``, out: `C:/`, err: false}, } From c5d18ea23778019d576b679481c2f39c9815dbcc Mon Sep 17 00:00:00 2001 From: Moss Date: Wed, 13 May 2020 11:52:16 +0200 Subject: [PATCH 102/109] Update Detect interface comment --- get.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/get.go b/get.go index 424d7c717..3ee34e7b8 100644 --- a/get.go +++ b/get.go @@ -43,8 +43,11 @@ type Getter interface { // allow clients to let the getters decide which mode to use. Mode(context.Context, *url.URL) (Mode, error) - // Detect will detect whether the string matches a known pattern to + // Detect detects whether the Request.Src matches a known pattern to // turn it into a proper URL. + // The Getter must validate if the Request.Src is a valid URL + // with a valid scheme for the Getter, and also check if the + // current Getter is the forced one and return true if that's the case. Detect(*Request) (bool, error) } From 7327304c4db9654cff8cd12327a6aff03c4ec4fc Mon Sep 17 00:00:00 2001 From: Moss Date: Wed, 13 May 2020 13:54:24 +0200 Subject: [PATCH 103/109] Update SmbMountGetter --- get_smb_mount.go | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/get_smb_mount.go b/get_smb_mount.go index 6d1050bab..5e6491e31 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -6,13 +6,15 @@ import ( "net/url" "os" "path/filepath" - "regexp" "runtime" "strings" ) // SmbMountGetter is a Getter implementation that will download a module from -// a shared folder using the file system. +// a shared folder using the file system using FileGetter implementation. +// For Unix and MacOS users, the Getter will look for usual system specific mount paths such as: +// /Volumes/ for MacOS +// /run/user/1000/gvfs/smb-share:server=,share= for Unix type SmbMountGetter struct { } @@ -60,26 +62,11 @@ func (g *SmbMountGetter) findPrefixAndPath(u *url.URL) (string, string) { path = filepath.Join("Volumes", u.Path) case "linux": prefix = string(os.PathSeparator) - share := g.findShare(u) - pwd := fmt.Sprintf("run/user/1000/gvfs/smb-share:server=%s,share=%s", u.Host, share) - path = filepath.Join(pwd, u.Path) + path = fmt.Sprintf("run/user/1000/gvfs/smb-share:server=%s,share=%s", u.Host, strings.TrimPrefix(u.Path, prefix)) } return prefix, path } -func (g *SmbMountGetter) findShare(u *url.URL) string { - // Get shared directory - path := strings.TrimPrefix(u.Path, "/") - splt := regexp.MustCompile(`/`) - directories := splt.Split(path, 2) - - if len(directories) > 0 { - return directories[0] - } - - return "." -} - func (g *SmbMountGetter) Detect(req *Request) (bool, error) { if len(req.Src) == 0 { return false, nil From 209cb282fedb428ecab02bca844854409d51dd10 Mon Sep 17 00:00:00 2001 From: Moss Date: Wed, 13 May 2020 15:14:30 +0200 Subject: [PATCH 104/109] Update README --- README.md | 35 +++++++++++++++++++++-------------- get_smb_mount.go | 2 +- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 134fa1402..0962a161e 100644 --- a/README.md +++ b/README.md @@ -364,14 +364,26 @@ The tests for `get_gcs.go` require you to have GCP credentials set in your envir ### SMB (smb) -On Unix, the SMB getter will try to get files from samba using the [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) when a url is prefixed with `smb://`, for example `smb://foo/bar/dir`. -For a local mount, the FileGetter must be used instead. -⚠️ The [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) needs to be installed for this to work on Unix. - -On Windows, the SMB getter will try to get files using the windows file system when the url is prefixed with `smb://`, `//`, or `\\`. This will be done by using the existing FileGetter. -⚠️ A FileGetter is necessary for this to work on Windows. - -The following examples work for Windows and Unix: +There are two options that go-getter will use to download a file in a smb shared folder. The first option uses +[`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) and the second one uses the file system +to look for a file in a local mount of the shared folder in the OS specific volume folder. go-getter will try to download +files from a smb shared folder whenever the url is prefixed with `smb://`. + +⚠️ The [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) command is available only for Unix. +This is the best option for a Unix user and therefore the client must be installed. + +The `smbclient` cli is not available for Windows and MacOS. Whenever the `smbclient` is not available, go-getter +will try to get files using the file system, when this happens the getter uses the FileGetter implementation. +⚠️ The FileGetter need to be enabled for this to work on Windows and MacOS. + +When connecting to a smb server, the OS creates a local mount in a system specific volume folder, and go-getter will +try to access the following folders when looking for local mounts. + +- MacOS: /Volumes/ +- Windows: \\\\\\\\ +- Unix: /run/user/1000/gvfs/smb-share:server=\,share= + +The following examples work for all the OSes: - smb://host/shared/dir (downloads directory content) - smb://host/shared/dir/file (downloads file) @@ -381,13 +393,8 @@ The following examples work for Unix: - smb://username:password@host/shared/dir/file (downloads file) - smb://username@host/shared/dir/file -⚠️ The above examples also work on Windows but the authentication is not use to access the file system. +⚠️ The above examples also work on the other OSes but the authentication is not used to access the file system. -These examples only work for Windows: -- //host/shared/dir (downloads directory content) -- //host/shared/dir/file (downloads file) -- \\\host\shared\dir (downloads directory content) -- \\\host\shared\dir\file (downloads file) #### SMB Testing diff --git a/get_smb_mount.go b/get_smb_mount.go index 5e6491e31..3d91794ff 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -14,7 +14,7 @@ import ( // a shared folder using the file system using FileGetter implementation. // For Unix and MacOS users, the Getter will look for usual system specific mount paths such as: // /Volumes/ for MacOS -// /run/user/1000/gvfs/smb-share:server=,share= for Unix +// /run/user/1000/gvfs/smb-share:server=,share= for Unix type SmbMountGetter struct { } From a3dc9e0d170d4e56c48b768a1fa4f729f1c22767 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 19 May 2020 14:41:42 +0200 Subject: [PATCH 105/109] Test refactoring circleci config --- .circleci/config.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bffcf4a01..c25fc3ced 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -175,20 +175,14 @@ jobs: path: *WIN_TEST_RESULTS go-smb-test: - working_directory: /go-getter docker: - - image: docker:17.05.0-ce-git + - image: circleci/golang:<< parameters.go-version >> + parameters: + go-version: + type: string steps: - checkout - setup_remote_docker - - run: - name: install dependencies - command: | - apk add --no-cache \ - py-pip=9.0.0-r1 - pip install \ - docker-compose==1.12.0 - - run: name: build and start smb server and gogetter containers command: | @@ -220,4 +214,7 @@ workflows: go-version: ["1.14.1"] gotestsum-version: ["0.4.1"] name: win-test-go-<< matrix.go-version >> - - go-smb-test + - go-smb-test: + matrix: + parameters: + go-version: ["1.14.1"] From 95f553a6d8e1468cc06a17e89d5232cb8caa9551 Mon Sep 17 00:00:00 2001 From: Moss Date: Thu, 28 May 2020 18:50:51 +0200 Subject: [PATCH 106/109] move detectors back and add them to getters --- Dockerfile | 2 +- Dockerfile-smbserver | 2 +- Makefile | 2 +- client.go | 339 +++++++++++++++++++++--------------------- detect.go | 9 ++ detect_bitbucket.go | 66 ++++++++ detect_file.go | 61 ++++++++ detect_git.go | 26 ++++ detect_github.go | 47 ++++++ detect_test.go | 33 ++-- docker-compose.yml | 2 +- gcs/detect_gcs.go | 43 ++++++ gcs/get_gcs.go | 32 +--- get.go | 12 +- get_file.go | 51 +------ get_git.go | 101 ++----------- get_git_test.go | 26 +++- get_hg.go | 12 +- get_smb_mount.go | 3 +- get_smbclient.go | 3 +- get_smbclient_test.go | 16 +- request.go | 3 +- s3/detect_s3.go | 61 ++++++++ s3/get_s3.go | 52 +------ source.go | 1 + 25 files changed, 596 insertions(+), 409 deletions(-) create mode 100644 detect_bitbucket.go create mode 100644 detect_file.go create mode 100644 detect_git.go create mode 100644 detect_github.go create mode 100644 gcs/detect_gcs.go create mode 100644 s3/detect_s3.go diff --git a/Dockerfile b/Dockerfile index 80b4612ec..7c3dd846e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,4 +6,4 @@ WORKDIR /go-getter RUN go mod download RUN apt-get update -RUN apt-get -y install smbclient \ No newline at end of file +RUN apt-get -y install smbclient diff --git a/Dockerfile-smbserver b/Dockerfile-smbserver index ff7229834..48f68f450 100644 --- a/Dockerfile-smbserver +++ b/Dockerfile-smbserver @@ -6,4 +6,4 @@ RUN mkdir -p /public && mkdir -p /private # Create shared files and directories under the shared folders (data and mnt) RUN echo 'Hello' > /public/file.txt && mkdir -p /public/subdir && echo 'Hello' > /public/subdir/file.txt -RUN echo 'Hello' > /private/file.txt && mkdir -p /private/subdir && echo 'Hello' > /private/subdir/file.txt \ No newline at end of file +RUN echo 'Hello' > /private/file.txt && mkdir -p /private/subdir && echo 'Hello' > /private/subdir/file.txt diff --git a/Makefile b/Makefile index 5e58ecbb8..f920d84f1 100644 --- a/Makefile +++ b/Makefile @@ -9,4 +9,4 @@ smbtests-prepare: smbtests: @docker cp ./ gogetter:/go-getter/ - @docker exec -it gogetter bash -c "env ACC_SMB_TEST=1 go test -v ./... -run=TestSmb_" \ No newline at end of file + @docker exec -it gogetter bash -c "env ACC_SMB_TEST=1 go test -v ./... -run=TestSmb_" diff --git a/client.go b/client.go index 91274f2df..153b81607 100644 --- a/client.go +++ b/client.go @@ -3,7 +3,6 @@ package getter import ( "context" "fmt" - "github.com/hashicorp/go-multierror" "io/ioutil" "os" "path/filepath" @@ -11,6 +10,7 @@ import ( "strings" urlhelper "github.com/hashicorp/go-getter/v2/helper/url" + "github.com/hashicorp/go-multierror" safetemp "github.com/hashicorp/go-safetemp" ) @@ -24,7 +24,7 @@ type Client struct { // If this is nil, then the default value is the Decompressors global. Decompressors map[string]Decompressor - // Getters is the map of protocols supported by this client. If this + // Getters is the list of protocols supported by this client. If this // is nil, then the default Getters variable will be used. Getters []Getter } @@ -48,16 +48,15 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { // If there is a subdir component, then we download the root separately // and then copy over the proper subdir. - var realDst, subDir string - req.Src, subDir = SourceDirSubdir(req.Src) - if subDir != "" { + req.Src, req.subDir = SourceDirSubdir(req.Src) + if req.subDir != "" { td, tdcloser, err := safetemp.Dir("", "getter") if err != nil { return nil, err } defer tdcloser.Close() - realDst = req.Dst + req.realDst = req.Dst req.Dst = td } @@ -71,206 +70,210 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { continue } - u, err := urlhelper.Parse(req.Src) - req.u = u + result, fatal, err := c.get(ctx, req, g) if err != nil { - return nil, err - } - - // We have magic query parameters that we use to signal different features - q := req.u.Query() - - // Determine if we have an archive type - archiveV := q.Get("archive") - if archiveV != "" { - // Delete the paramter since it is a magic parameter we don't - // want to pass on to the Getter - q.Del("archive") - req.u.RawQuery = q.Encode() - - // If we can parse the value as a bool and it is false, then - // set the archive to "-" which should never map to a decompressor - if b, err := strconv.ParseBool(archiveV); err == nil && !b { - archiveV = "-" - } - } else { - // We don't appear to... but is it part of the filename? - matchingLen := 0 - for k := range c.Decompressors { - if strings.HasSuffix(req.u.Path, "."+k) && len(k) > matchingLen { - archiveV = k - matchingLen = len(k) - } + if fatal { + return nil, err } + multierr = append(multierr, err) + continue } - // If we have a decompressor, then we need to change the destination - // to download to a temporary path. We unarchive this into the final, - // real path. - var decompressDst, td string - var decompressDir bool - decompressor := c.Decompressors[archiveV] - if decompressor != nil { - // Create a temporary directory to store our archive. We delete - // this at the end of everything. - td, err := ioutil.TempDir("", "getter") - if err != nil { - return nil, fmt.Errorf( - "Error creating temporary directory for archive: %s", err) - } - // If success clean this after loop - defer os.RemoveAll(td) - - // Swap the download directory to be our temporary path and - // store the old values. - decompressDst = req.Dst - decompressDir = req.Mode != ModeFile - req.Dst = filepath.Join(td, "archive") - req.Mode = ModeFile - } + return result, nil + } - // Determine checksum if we have one - checksum, err := c.GetChecksum(ctx, req) - if err != nil { - return nil, fmt.Errorf("invalid checksum: %s", err) - } + if len(multierr) == 1 { + // This is for keeping the error original format + return nil, multierr[0] + } - // Delete the query parameter if we have it. - q.Del("checksum") - req.u.RawQuery = q.Encode() + if multierr != nil { + var result *multierror.Error + result = multierror.Append(result, multierr...) + return nil, fmt.Errorf("error downloading '%s': %s", req.Src, result.Error()) + } - // From now one, each possible getter will - // try to run the rest of the logic + return nil, fmt.Errorf("error downloading '%s'", req.Src) +} - if req.Mode == ModeAny { - // Ask the getter which client mode to use - req.Mode, err = g.Mode(ctx, req.u) - if err != nil { - os.RemoveAll(td) // Removes temporary folder - multierr = append(multierr, err) - continue +func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, bool, error) { + u, err := urlhelper.Parse(req.Src) + req.u = u + if err != nil { + return nil, true, err + } + + // We have magic query parameters that we use to signal different features + q := req.u.Query() + + // Determine if we have an archive type + archiveV := q.Get("archive") + if archiveV != "" { + // Delete the paramter since it is a magic parameter we don't + // want to pass on to the Getter + q.Del("archive") + req.u.RawQuery = q.Encode() + + // If we can parse the value as a bool and it is false, then + // set the archive to "-" which should never map to a decompressor + if b, err := strconv.ParseBool(archiveV); err == nil && !b { + archiveV = "-" + } + } else { + // We don't appear to... but is it part of the filename? + matchingLen := 0 + for k := range c.Decompressors { + if strings.HasSuffix(req.u.Path, "."+k) && len(k) > matchingLen { + archiveV = k + matchingLen = len(k) } + } + } - // Destination is the base name of the URL path in "any" mode when - // a file source is detected. - if req.Mode == ModeFile { - filename := filepath.Base(req.u.Path) + // If we have a decompressor, then we need to change the destination + // to download to a temporary path. We unarchive this into the final, + // real path. + var decompressDst string + var decompressDir bool + decompressor := c.Decompressors[archiveV] + if decompressor != nil { + // Create a temporary directory to store our archive. We delete + // this at the end of everything. + td, err := ioutil.TempDir("", "getter") + if err != nil { + return nil, true, fmt.Errorf( + "Error creating temporary directory for archive: %s", err) + } + defer os.RemoveAll(td) + + // Swap the download directory to be our temporary path and + // store the old values. + decompressDst = req.Dst + decompressDir = req.Mode != ModeFile + req.Dst = filepath.Join(td, "archive") + req.Mode = ModeFile + } - // Determine if we have a custom file name - if v := q.Get("filename"); v != "" { - // Delete the query parameter if we have it. - q.Del("filename") - req.u.RawQuery = q.Encode() + // Determine checksum if we have one + checksum, err := c.GetChecksum(ctx, req) + if err != nil { + return nil, true, fmt.Errorf("invalid checksum: %s", err) + } - filename = v - } + // Delete the query parameter if we have it. + q.Del("checksum") + req.u.RawQuery = q.Encode() - req.Dst = filepath.Join(req.Dst, filename) - } + if req.Mode == ModeAny { + // Ask the getter which client mode to use + req.Mode, err = g.Mode(ctx, req.u) + if err != nil { + return nil, false, err } - // If we're not downloading a directory, then just download the file - // and return. + // Destination is the base name of the URL path in "any" mode when + // a file source is detected. if req.Mode == ModeFile { - getFile := true - if checksum != nil { - if err := checksum.Checksum(req.Dst); err == nil { - // don't get the file if the checksum of dst is correct - getFile = false - } - } - if getFile { - if err := g.GetFile(ctx, req); err != nil { - os.RemoveAll(td) - multierr = append(multierr, err) - continue - } + filename := filepath.Base(req.u.Path) - if checksum != nil { - if err := checksum.Checksum(req.Dst); err != nil { - return nil, err - } - } - } - - if decompressor != nil { - // We have a decompressor, so decompress the current destination - // into the final destination with the proper mode. - err := decompressor.Decompress(decompressDst, req.Dst, decompressDir) - if err != nil { - return nil, err - } + // Determine if we have a custom file name + if v := q.Get("filename"); v != "" { + // Delete the query parameter if we have it. + q.Del("filename") + req.u.RawQuery = q.Encode() - // Swap the information back - req.Dst = decompressDst - if decompressDir { - req.Mode = ModeAny - } else { - req.Mode = ModeFile - } + filename = v } - // We check the dir value again because it can be switched back - // if we were unarchiving. If we're still only Get-ing a file, then - // we're done. - if req.Mode == ModeFile { - return &GetResult{req.Dst}, nil - } + req.Dst = filepath.Join(req.Dst, filename) } + } - // If we're at this point we're either downloading a directory or we've - // downloaded and unarchived a directory and we're just checking subdir. - // In the case we have a decompressor we don't Get because it was Get - // above. - if decompressor == nil { - // If we're getting a directory, then this is an error. You cannot - // checksum a directory. TODO: test - if checksum != nil { - return nil, fmt.Errorf( - "checksum cannot be specified for directory download") + // If we're not downloading a directory, then just download the file + // and return. + if req.Mode == ModeFile { + getFile := true + if checksum != nil { + if err := checksum.Checksum(req.Dst); err == nil { + // don't get the file if the checksum of dst is correct + getFile = false + } + } + if getFile { + if err := g.GetFile(ctx, req); err != nil { + return nil, false, err } - // We're downloading a directory, which might require a bit more work - // if we're specifying a subdir. - if err := g.Get(ctx, req); err != nil { - os.RemoveAll(td) // Removes temporary folder - multierr = append(multierr, err) - continue + if checksum != nil { + if err := checksum.Checksum(req.Dst); err != nil { + return nil, true, err + } } } - // If we have a subdir, copy that over - if subDir != "" { - if err := os.RemoveAll(realDst); err != nil { - return nil, err - } - if err := os.MkdirAll(realDst, 0755); err != nil { - return nil, err + if decompressor != nil { + // We have a decompressor, so decompress the current destination + // into the final destination with the proper mode. + err := decompressor.Decompress(decompressDst, req.Dst, decompressDir) + if err != nil { + return nil, true, err } - // Process any globs - subDir, err := SubdirGlob(req.Dst, subDir) - if err != nil { - return nil, err + // Swap the information back + req.Dst = decompressDst + if decompressDir { + req.Mode = ModeAny + } else { + req.Mode = ModeFile } + } - return &GetResult{realDst}, copyDir(ctx, realDst, subDir, false) + // We check the dir value again because it can be switched back + // if we were unarchiving. If we're still only Get-ing a file, then + // we're done. + if req.Mode == ModeFile { + return &GetResult{req.Dst}, false, nil } - return &GetResult{req.Dst}, nil } - if len(multierr) == 1 { - // This is for keeping the error original format - return nil, multierr[0] + // If we're at this point we're either downloading a directory or we've + // downloaded and unarchived a directory and we're just checking subdir. + // In the case we have a decompressor we don't Get because it was Get + // above. + if decompressor == nil { + // If we're getting a directory, then this is an error. You cannot + // checksum a directory. TODO: test + if checksum != nil { + return nil, true, fmt.Errorf( + "checksum cannot be specified for directory download") + } + + // We're downloading a directory, which might require a bit more work + // if we're specifying a subdir. + if err := g.Get(ctx, req); err != nil { + return nil, false, err + } } - if multierr != nil { - var result *multierror.Error - result = multierror.Append(result, multierr...) - return nil, fmt.Errorf("error downloading '%s': %s", req.Src, result.Error()) + // If we have a subdir, copy that over + if req.subDir != "" { + if err := os.RemoveAll(req.realDst); err != nil { + return nil, true, err + } + if err := os.MkdirAll(req.realDst, 0755); err != nil { + return nil, true, err + } + + // Process any globs + subDir, err := SubdirGlob(req.Dst, req.subDir) + if err != nil { + return nil, true, err + } + + return &GetResult{req.realDst}, false, copyDir(ctx, req.realDst, subDir, false) } + return &GetResult{req.Dst}, false, nil - return nil, fmt.Errorf("error downloading '%s'", req.Src) } func (c *Client) checkArchive(req *Request) string { diff --git a/detect.go b/detect.go index 3ac8a0384..062b849f1 100644 --- a/detect.go +++ b/detect.go @@ -5,6 +5,15 @@ import ( "github.com/hashicorp/go-getter/helper/url" ) +// Detector defines the interface that an invalid URL or a URL with a blank +// scheme is passed through in order to determine if its shorthand for +// something else well-known. +type Detector interface { + // Detect will detect whether the string matches a known pattern to + // turn it into a proper URL. + Detect(string, string) (string, bool, error) +} + // Detect is a method used to detect if a Getter is a candidate for downloading an artifact // by calling the Getter.Detect(*Request) method func Detect(req *Request, getter Getter) (bool, error) { diff --git a/detect_bitbucket.go b/detect_bitbucket.go new file mode 100644 index 000000000..19047eb19 --- /dev/null +++ b/detect_bitbucket.go @@ -0,0 +1,66 @@ +package getter + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" +) + +// BitBucketDetector implements Detector to detect BitBucket URLs and turn +// them into URLs that the Git or Hg Getter can understand. +type BitBucketDetector struct{} + +func (d *BitBucketDetector) Detect(src, _ string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + if strings.HasPrefix(src, "bitbucket.org/") { + return d.detectHTTP(src) + } + + return "", false, nil +} + +func (d *BitBucketDetector) detectHTTP(src string) (string, bool, error) { + u, err := url.Parse("https://" + src) + if err != nil { + return "", true, fmt.Errorf("error parsing BitBucket URL: %s", err) + } + + // We need to get info on this BitBucket repository to determine whether + // it is Git or Hg. + var info struct { + SCM string `json:"scm"` + } + infoUrl := "https://api.bitbucket.org/2.0/repositories" + u.Path + resp, err := http.Get(infoUrl) + if err != nil { + return "", true, fmt.Errorf("error looking up BitBucket URL: %s", err) + } + if resp.StatusCode == 403 { + // A private repo + return "", true, fmt.Errorf( + "shorthand BitBucket URL can't be used for private repos, " + + "please use a full URL") + } + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&info); err != nil { + return "", true, fmt.Errorf("error looking up BitBucket URL: %s", err) + } + + switch info.SCM { + case "git": + if !strings.HasSuffix(u.Path, ".git") { + u.Path += ".git" + } + + return "git::" + u.String(), true, nil + case "hg": + return "hg::" + u.String(), true, nil + default: + return "", true, fmt.Errorf("unknown BitBucket SCM type: %s", info.SCM) + } +} diff --git a/detect_file.go b/detect_file.go new file mode 100644 index 000000000..8a0256e77 --- /dev/null +++ b/detect_file.go @@ -0,0 +1,61 @@ +package getter + +import ( + "fmt" + "os" + "path/filepath" + "runtime" +) + +// FileDetector implements Detector to detect file paths. +type FileDetector struct{} + +func (d *FileDetector) Detect(src, pwd string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + if !filepath.IsAbs(src) { + if pwd == "" { + return "", true, fmt.Errorf( + "relative paths require a module with a pwd") + } + + // Stat the pwd to determine if its a symbolic link. If it is, + // then the pwd becomes the original directory. Otherwise, + // `filepath.Join` below does some weird stuff. + // + // We just ignore if the pwd doesn't exist. That error will be + // caught later when we try to use the URL. + if fi, err := os.Lstat(pwd); !os.IsNotExist(err) { + if err != nil { + return "", true, err + } + if fi.Mode()&os.ModeSymlink != 0 { + pwd, err = filepath.EvalSymlinks(pwd) + if err != nil { + return "", true, err + } + + // The symlink itself might be a relative path, so we have to + // resolve this to have a correctly rooted URL. + pwd, err = filepath.Abs(pwd) + if err != nil { + return "", true, err + } + } + } + + src = filepath.Join(pwd, src) + } + + return fmtFileURL(src), true, nil +} + +func fmtFileURL(path string) string { + if runtime.GOOS == "windows" { + // Make sure we're using "/" on Windows. URLs are "/"-based. + path = filepath.ToSlash(path) + } + return path +} diff --git a/detect_git.go b/detect_git.go new file mode 100644 index 000000000..eeb8a04c5 --- /dev/null +++ b/detect_git.go @@ -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 +} diff --git a/detect_github.go b/detect_github.go new file mode 100644 index 000000000..4bf4daf23 --- /dev/null +++ b/detect_github.go @@ -0,0 +1,47 @@ +package getter + +import ( + "fmt" + "net/url" + "strings" +) + +// GitHubDetector implements Detector to detect GitHub URLs and turn +// them into URLs that the Git Getter can understand. +type GitHubDetector struct{} + +func (d *GitHubDetector) Detect(src, _ string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + if strings.HasPrefix(src, "github.com/") { + return d.detectHTTP(src) + } + + return "", false, nil +} + +func (d *GitHubDetector) detectHTTP(src string) (string, bool, error) { + parts := strings.Split(src, "/") + if len(parts) < 3 { + return "", false, fmt.Errorf( + "GitHub URLs should be github.com/username/repo") + } + + urlStr := fmt.Sprintf("https://%s", strings.Join(parts[:3], "/")) + url, err := url.Parse(urlStr) + if err != nil { + return "", true, fmt.Errorf("error parsing GitHub URL: %s", err) + } + + if !strings.HasSuffix(url.Path, ".git") { + url.Path += ".git" + } + + if len(parts) > 3 { + url.Path += "//" + strings.Join(parts[3:], "/") + } + + return "git::" + url.String(), true, nil +} diff --git a/detect_test.go b/detect_test.go index c69fb6ae8..bc7933a89 100644 --- a/detect_test.go +++ b/detect_test.go @@ -6,6 +6,12 @@ import ( ) func TestDetect(t *testing.T) { + gitGetter := &GitGetter{[]Detector{ + new(GitDetector), + new(BitBucketDetector), + new(GitHubDetector), + }, + } cases := []struct { Input string Pwd string @@ -14,13 +20,13 @@ func TestDetect(t *testing.T) { getter Getter }{ {"./foo", "/foo", "/foo/foo", false, new(FileGetter)}, - {"git::./foo", "/foo", "/foo/foo", false, new(GitGetter)}, + {"git::./foo", "/foo", "/foo/foo", false, gitGetter}, { "git::github.com/hashicorp/foo", "", "https://github.com/hashicorp/foo.git", false, - new(GitGetter), + gitGetter, }, { "./foo", @@ -34,21 +40,21 @@ func TestDetect(t *testing.T) { "", "https://github.com/hashicorp/consul.git", false, - new(GitGetter), + gitGetter, }, { - "git::https://person@someothergit.com/foo/bar", // this + "git::https://person@someothergit.com/foo/bar", "", "https://person@someothergit.com/foo/bar", false, - new(GitGetter), + gitGetter, }, { - "git::https://person@someothergit.com/foo/bar", // this + "git::https://person@someothergit.com/foo/bar", "/bar", "https://person@someothergit.com/foo/bar", false, - new(GitGetter), + gitGetter, }, // https://github.com/hashicorp/go-getter/pull/124 @@ -57,21 +63,21 @@ func TestDetect(t *testing.T) { "", "ssh://git@my.custom.git/dir1/dir2", false, - new(GitGetter), + gitGetter, }, { "git::git@my.custom.git:dir1/dir2", "/foo", "ssh://git@my.custom.git/dir1/dir2", false, - new(GitGetter), + gitGetter, }, { "git::git@my.custom.git:dir1/dir2", "", "ssh://git@my.custom.git/dir1/dir2", false, - new(GitGetter), + gitGetter, }, } @@ -81,10 +87,15 @@ func TestDetect(t *testing.T) { Src: tc.Input, Pwd: tc.Pwd, } - _, err := Detect(req, tc.getter) + ok, err := Detect(req, tc.getter) if err != nil != tc.Err { t.Fatalf("%d: bad err: %s", i, err) } + + if !tc.Err && !ok { + t.Fatalf("%d: should be ok", i) + } + if req.Src != tc.Output { t.Fatalf("%d: bad output: %s\nexpected: %s", i, req.Src, tc.Output) } diff --git a/docker-compose.yml b/docker-compose.yml index 75799bc54..008c413ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,4 +31,4 @@ services: command: tail -f /dev/null networks: - default: \ No newline at end of file + default: diff --git a/gcs/detect_gcs.go b/gcs/detect_gcs.go new file mode 100644 index 000000000..d5846ec4b --- /dev/null +++ b/gcs/detect_gcs.go @@ -0,0 +1,43 @@ +package gcs + +import ( + "fmt" + "net/url" + "strings" +) + +// Detector implements Detector to detect GCS URLs and turn +// them into URLs that the GCSGetter can understand. +type Detector struct{} + +func (d *Detector) Detect(src, _ string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + if strings.Contains(src, "googleapis.com/") { + return d.detectHTTP(src) + } + + return "", false, nil +} + +func (d *Detector) detectHTTP(src string) (string, bool, error) { + + parts := strings.Split(src, "/") + if len(parts) < 5 { + return "", false, fmt.Errorf( + "URL is not a valid GCS URL") + } + version := parts[2] + bucket := parts[3] + object := strings.Join(parts[4:], "/") + + url, err := url.Parse(fmt.Sprintf("https://www.googleapis.com/storage/%s/%s/%s", + version, bucket, object)) + if err != nil { + return "", false, fmt.Errorf("error parsing GCS URL: %s", err) + } + + return url.String(), true, nil +} diff --git a/gcs/get_gcs.go b/gcs/get_gcs.go index 8ee68fbd0..dd9b6f256 100644 --- a/gcs/get_gcs.go +++ b/gcs/get_gcs.go @@ -15,8 +15,7 @@ import ( // Getter is a Getter implementation that will download a module from // a GCS bucket. -type Getter struct { -} +type Getter struct {} func (g *Getter) Mode(ctx context.Context, u *url.URL) (getter.Mode, error) { @@ -195,33 +194,16 @@ func (g *Getter) Detect(req *getter.Request) (bool, error) { return false, nil } - if strings.Contains(src, "googleapis.com/") { - src, ok, err := g.detectHTTP(src) - req.Src = src + src, ok, err := new(Detector).Detect(src, req.Pwd) + if err != nil { return ok, err } - - return false, nil -} - -func (g *Getter) detectHTTP(src string) (string, bool, error) { - - parts := strings.Split(src, "/") - if len(parts) < 5 { - return "", false, fmt.Errorf( - "URL is not a valid GCS URL") - } - version := parts[2] - bucket := parts[3] - object := strings.Join(parts[4:], "/") - - url, err := url.Parse(fmt.Sprintf("https://www.googleapis.com/storage/%s/%s/%s", - version, bucket, object)) - if err != nil { - return "", false, fmt.Errorf("error parsing GCS URL: %s", err) + if ok { + req.Src = src + return ok, nil } - return url.String(), true, nil + return false, nil } func (g *Getter) validScheme(scheme string) bool { diff --git a/get.go b/get.go index 3ee34e7b8..c599a6f6a 100644 --- a/get.go +++ b/get.go @@ -44,7 +44,8 @@ type Getter interface { Mode(context.Context, *url.URL) (Mode, error) // Detect detects whether the Request.Src matches a known pattern to - // turn it into a proper URL. + // turn it into a proper URL, and also transforms and update Request.Src + // when necessary. // The Getter must validate if the Request.Src is a valid URL // with a valid scheme for the Getter, and also check if the // current Getter is the forced one and return true if that's the case. @@ -72,8 +73,15 @@ func init() { Netrc: true, } + // The order of the Getters in the list may affect the result + // depending if the Request.Src is detected as valid by multiple getters Getters = []Getter{ - new(GitGetter), + &GitGetter{[]Detector{ + new(GitHubDetector), + new(GitDetector), + new(BitBucketDetector), + }, + }, new(HgGetter), new(SmbClientGetter), new(SmbMountGetter), diff --git a/get_file.go b/get_file.go index ba096efac..6c1e2ded1 100644 --- a/get_file.go +++ b/get_file.go @@ -11,8 +11,7 @@ import ( // FileGetter is a Getter implementation that will download a module from // a file scheme. -type FileGetter struct { -} +type FileGetter struct {} func (g *FileGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { path := u.Path @@ -185,52 +184,18 @@ func (g *FileGetter) Detect(req *Request) (bool, error) { // e.g. C:/foo/bar for other cases a prefix file:// is necessary } - if !filepath.IsAbs(src) { - if pwd == "" { - return true, fmt.Errorf( - "relative paths require a module with a pwd") - } - - // Stat the pwd to determine if its a symbolic link. If it is, - // then the pwd becomes the original directory. Otherwise, - // `filepath.Join` below does some weird stuff. - // - // We just ignore if the pwd doesn't exist. That error will be - // caught later when we try to use the URL. - if fi, err := os.Lstat(pwd); !os.IsNotExist(err) { - if err != nil { - return true, err - } - if fi.Mode()&os.ModeSymlink != 0 { - pwd, err = filepath.EvalSymlinks(pwd) - if err != nil { - return true, err - } - - // The symlink itself might be a relative path, so we have to - // resolve this to have a correctly rooted URL. - pwd, err = filepath.Abs(pwd) - if err != nil { - return true, err - } - } - } - - src = filepath.Join(pwd, src) + src, ok, err := new(FileDetector).Detect(src, pwd) + if err != nil { + return ok, err + } + if ok { + req.Src = src + return ok, nil } - req.Src = fmtFileURL(src) return true, nil } func (g *FileGetter) validScheme(scheme string) bool { return scheme == "file" } - -func fmtFileURL(path string) string { - if runtime.GOOS == "windows" { - // Make sure we're using "/" on Windows. URLs are "/"-based. - path = filepath.ToSlash(path) - } - return path -} diff --git a/get_git.go b/get_git.go index d30abfde2..314339129 100644 --- a/get_git.go +++ b/get_git.go @@ -4,10 +4,8 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" "fmt" "io/ioutil" - "net/http" "net/url" "os" "os/exec" @@ -25,6 +23,7 @@ import ( // GitGetter is a Getter implementation that will download a module from // a git repository. type GitGetter struct { + Detectors []Detector } var defaultBranchRegexp = regexp.MustCompile(`\s->\sorigin/(.*)`) @@ -345,37 +344,16 @@ func (g *GitGetter) Detect(req *Request) (bool, error) { return false, nil } - u, err = detectSSH(src) - if err != nil { - return true, err - } - if u != nil { - // We require the username to be "git" to assume that this is a Git URL - if u.User.Username() == "git" { - req.Src = u.String() - return true, nil + for _, d := range g.Detectors { + src, ok, err := d.Detect(src, req.Pwd) + if err != nil { + return ok, err } - } - - result, ok, err := detectGitHub(src) - if err != nil { - return ok, err - } - if ok { - req.Src = result - return ok, nil - } - - result, u, err = detectBitBucket(src) - if err != nil { - return true, err - } - if result == "git" { - if !strings.HasSuffix(u.Path, ".git") { - u.Path += ".git" + forced, src := getForcedGetter(src) + if ok && g.validScheme(forced) { + req.Src = src + return ok, nil } - req.Src = u.String() - return true, nil } if _, err = url.Parse(req.Src); err != nil { @@ -396,67 +374,6 @@ func (g *GitGetter) Detect(req *Request) (bool, error) { return false, nil } -func detectBitBucket(src string) (string, *url.URL, error) { - if !strings.HasPrefix(src, "bitbucket.org/") { - return "", nil, nil - } - - u, err := url.Parse("https://" + src) - if err != nil { - return "", nil, fmt.Errorf("error parsing BitBucket URL: %s", err) - } - - // We need to get info on this BitBucket repository to determine whether - // it is Git or Hg. - var info struct { - SCM string `json:"scm"` - } - infoUrl := "https://api.bitbucket.org/2.0/repositories" + u.Path - resp, err := http.Get(infoUrl) - if err != nil { - return "", nil, fmt.Errorf("error looking up BitBucket URL: %s", err) - } - if resp.StatusCode == 403 { - // A private repo - return "", nil, fmt.Errorf( - "shorthand BitBucket URL can't be used for private repos, " + - "please use a full URL") - } - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&info); err != nil { - return "", nil, fmt.Errorf("error looking up BitBucket URL: %s", err) - } - - return info.SCM, u, nil -} - -func detectGitHub(src string) (string, bool, error) { - if !strings.HasPrefix(src, "github.com/") { - return "", false, nil - } - parts := strings.Split(src, "/") - if len(parts) < 3 { - return "", false, fmt.Errorf( - "GitHub URLs should be github.com/username/repo") - } - - urlStr := fmt.Sprintf("https://%s", strings.Join(parts[:3], "/")) - url, err := url.Parse(urlStr) - if err != nil { - return "", true, fmt.Errorf("error parsing GitHub URL: %s", err) - } - - if !strings.HasSuffix(url.Path, ".git") { - url.Path += ".git" - } - - if len(parts) > 3 { - url.Path += "//" + strings.Join(parts[3:], "/") - } - - return url.String(), true, nil -} - func (g *GitGetter) validScheme(scheme string) bool { return scheme == "git" || scheme == "ssh" } diff --git a/get_git_test.go b/get_git_test.go index 5a9597034..2b0275685 100644 --- a/get_git_test.go +++ b/get_git_test.go @@ -411,8 +411,14 @@ func TestGitGetter_sshSCPStyle(t *testing.T) { Mode: ModeDir, } + getter := &GitGetter{[]Detector{ + new(GitDetector), + new(BitBucketDetector), + new(GitHubDetector), + }, + } client := &Client{ - Getters: []Getter{new(GitGetter)}, + Getters: []Getter{getter}, } if _, err := client.Get(ctx, req); err != nil { @@ -617,7 +623,12 @@ func TestGitGetter_GitHubDetector(t *testing.T) { } pwd := "/pwd" - f := new(GitGetter) + f := &GitGetter{[]Detector{ + new(GitDetector), + new(BitBucketDetector), + new(GitHubDetector), + }, + } for i, tc := range cases { req := &Request{ Src: tc.Input, @@ -689,16 +700,25 @@ func TestGitGetter_Detector(t *testing.T) { } pwd := "/pwd" + getter := &GitGetter{[]Detector{ + new(GitDetector), + new(BitBucketDetector), + new(GitHubDetector), + }, + } for _, tc := range cases { t.Run(tc.Input, func(t *testing.T) { req := &Request{ Src: tc.Input, Pwd: pwd, } - _, err := Detect(req, new(GitGetter)) + ok, err := Detect(req, getter) if err != nil { t.Fatalf("unexpected error: %s", err) } + if !ok { + t.Fatalf("bad: should be ok") + } if req.Src != tc.Output { t.Errorf("wrong result\ninput: %s\ngot: %s\nwant: %s", tc.Input, req.Src, tc.Output) } diff --git a/get_hg.go b/get_hg.go index 8929a1c43..7d6f5a54f 100644 --- a/get_hg.go +++ b/get_hg.go @@ -16,6 +16,7 @@ import ( // HgGetter is a Getter implementation that will download a module from // a Mercurial repository. type HgGetter struct { + Detectors []Detector } func (g *HgGetter) Mode(ctx context.Context, _ *url.URL) (Mode, error) { @@ -154,13 +155,14 @@ func (g *HgGetter) Detect(req *Request) (bool, error) { return false, nil } - result, u, err := detectBitBucket(src) + src, ok, err := new(BitBucketDetector).Detect(src, req.Pwd) if err != nil { - return true, err + return ok, err } - if result == "hg" { - req.Src = u.String() - return true, nil + forced, src := getForcedGetter(src) + if ok && g.validScheme(forced) { + req.Src = src + return ok, nil } if isForcedGetter { diff --git a/get_smb_mount.go b/get_smb_mount.go index 3d91794ff..6c27e253a 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -15,8 +15,7 @@ import ( // For Unix and MacOS users, the Getter will look for usual system specific mount paths such as: // /Volumes/ for MacOS // /run/user/1000/gvfs/smb-share:server=,share= for Unix -type SmbMountGetter struct { -} +type SmbMountGetter struct{} func (g *SmbMountGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.Host == "" || u.Path == "" { diff --git a/get_smbclient.go b/get_smbclient.go index 02d0dbcbd..802cb9f2d 100644 --- a/get_smbclient.go +++ b/get_smbclient.go @@ -15,8 +15,7 @@ import ( // SmbClientGetter is a Getter implementation that will download a module from // a shared folder using smbclient cli. -type SmbClientGetter struct { -} +type SmbClientGetter struct{} func (g *SmbClientGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { if u.Host == "" || u.Path == "" { diff --git a/get_smbclient_test.go b/get_smbclient_test.go index 5506514d5..11c517be8 100644 --- a/get_smbclient_test.go +++ b/get_smbclient_test.go @@ -9,6 +9,15 @@ import ( "testing" ) +// smbTestsPreCheck checks whether ACC_SMB_TEST is set before running any SMB tests. +// SMB tests depends on a SMB server and should not run without the intention of it. +func smbTestsPreCheck(t *testing.T) { + r := os.Getenv("ACC_SMB_TEST") + if r != "1" { + t.Skip("smb getter tests won't run. ACC_SMB_TEST not set") + } +} + func TestSmb_GetterImpl(t *testing.T) { var _ Getter = new(SmbClientGetter) } @@ -318,10 +327,3 @@ func TestSmb_GetterMode(t *testing.T) { }) } } - -func smbTestsPreCheck(t *testing.T) { - r := os.Getenv("ACC_SMB_TEST") - if r != "1" { - t.Skip("smb getter tests won't run. ACC_SMB_TEST not set") - } -} diff --git a/request.go b/request.go index efb0838ed..df3224192 100644 --- a/request.go +++ b/request.go @@ -41,7 +41,8 @@ type Request struct { // By default a no op progress listener is used. ProgressListener ProgressTracker - u *url.URL + u *url.URL + subDir, realDst string } func (req *Request) URL() *url.URL { diff --git a/s3/detect_s3.go b/s3/detect_s3.go new file mode 100644 index 000000000..5749500ca --- /dev/null +++ b/s3/detect_s3.go @@ -0,0 +1,61 @@ +package s3 + +import ( + "fmt" + "net/url" + "strings" +) + +// Detector implements Detector to detect S3 URLs and turn +// them into URLs that the S3 getter can understand. +type Detector struct{} + +func (d *Detector) Detect(src, _ string) (string, bool, error) { + if len(src) == 0 { + return "", false, nil + } + + if strings.Contains(src, ".amazonaws.com/") { + return d.detectHTTP(src) + } + + return "", false, nil +} + +func (d *Detector) detectHTTP(src string) (string, bool, error) { + parts := strings.Split(src, "/") + if len(parts) < 2 { + return "", false, fmt.Errorf( + "URL is not a valid S3 URL") + } + + hostParts := strings.Split(parts[0], ".") + if len(hostParts) == 3 { + return d.detectPathStyle(hostParts[0], parts[1:]) + } else if len(hostParts) == 4 { + return d.detectVhostStyle(hostParts[1], hostParts[0], parts[1:]) + } else { + return "", false, fmt.Errorf( + "URL is not a valid S3 URL") + } +} + +func (d *Detector) detectPathStyle(region string, parts []string) (string, bool, error) { + urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s", region, strings.Join(parts, "/")) + url, err := url.Parse(urlStr) + if err != nil { + return "", false, fmt.Errorf("error parsing S3 URL: %s", err) + } + + return url.String(), true, nil +} + +func (d *Detector) detectVhostStyle(region, bucket string, parts []string) (string, bool, error) { + urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s/%s", region, bucket, strings.Join(parts, "/")) + url, err := url.Parse(urlStr) + if err != nil { + return "", false, fmt.Errorf("error parsing S3 URL: %s", err) + } + + return url.String(), true, nil +} diff --git a/s3/get_s3.go b/s3/get_s3.go index 70d9459af..d65ad71e2 100644 --- a/s3/get_s3.go +++ b/s3/get_s3.go @@ -20,8 +20,7 @@ import ( // Getter is a Getter implementation that will download a module from // a S3 bucket. -type Getter struct { -} +type Getter struct{} func (g *Getter) Mode(ctx context.Context, u *url.URL) (getter.Mode, error) { // Parse URL @@ -293,7 +292,7 @@ func (g *Getter) Detect(req *getter.Request) (bool, error) { if err == nil && u.Scheme != "" { if isForcedGetter { // Is the forced getter and source is a valid url - return true, nil + return true, nil } if g.validScheme(u.Scheme) { return true, nil @@ -302,51 +301,16 @@ func (g *Getter) Detect(req *getter.Request) (bool, error) { return false, nil } - if strings.Contains(src, ".amazonaws.com/") { - src, ok, err := g.detectHTTP(src) - req.Src = src - return ok, err - } - - return false, nil -} - -func (g *Getter) detectHTTP(src string) (string, bool, error) { - parts := strings.Split(src, "/") - if len(parts) < 2 { - return "", false, fmt.Errorf( - "URL is not a valid S3 URL") - } - - hostParts := strings.Split(parts[0], ".") - if len(hostParts) == 3 { - return g.detectPathStyle(hostParts[0], parts[1:]) - } else if len(hostParts) == 4 { - return g.detectVhostStyle(hostParts[1], hostParts[0], parts[1:]) - } else { - return "", false, fmt.Errorf( - "URL is not a valid S3 URL") - } -} - -func (g *Getter) detectPathStyle(region string, parts []string) (string, bool, error) { - urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s", region, strings.Join(parts, "/")) - url, err := url.Parse(urlStr) + src, ok, err := new(Detector).Detect(src, req.Pwd) if err != nil { - return "", false, fmt.Errorf("error parsing S3 URL: %s", err) + return ok, err } - - return url.String(), true, nil -} - -func (g *Getter) detectVhostStyle(region, bucket string, parts []string) (string, bool, error) { - urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s/%s", region, bucket, strings.Join(parts, "/")) - url, err := url.Parse(urlStr) - if err != nil { - return "", false, fmt.Errorf("error parsing S3 URL: %s", err) + if ok { + req.Src = src + return ok, nil } - return url.String(), true, nil + return false, nil } func (g *Getter) validScheme(scheme string) bool { diff --git a/source.go b/source.go index 23e70d77d..dab6d400c 100644 --- a/source.go +++ b/source.go @@ -15,6 +15,7 @@ import ( // proto://dom.com/path//path2?q=p => proto://dom.com/path?q=p, "path2" // func SourceDirSubdir(src string) (string, string) { + // URL might contains another url in query parameters stop := len(src) if idx := strings.Index(src, "?"); idx > -1 { From 7072459863d975f44a8d8972b343f12d30bd7432 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 2 Jun 2020 11:34:34 +0200 Subject: [PATCH 107/109] Add detector tests back --- detect_bitbucket_test.go | 67 +++++++++++++++++++++++ detect_file_test.go | 114 ++++++++++++++++++++++++++++++++++++++ detect_git_test.go | 63 +++++++++++++++++++++ detect_github_test.go | 44 +++++++++++++++ gcs/detect_gcs_test.go | 41 ++++++++++++++ gcs/get_gcs_test.go | 42 +------------- get_file.go | 2 +- get_file_test.go | 115 --------------------------------------- get_git_test.go | 4 ++ get_hg.go | 4 +- get_hg_test.go | 2 - s3/detect_s3_test.go | 84 ++++++++++++++++++++++++++++ s3/get_s3_test.go | 85 +---------------------------- 13 files changed, 421 insertions(+), 246 deletions(-) create mode 100644 detect_bitbucket_test.go create mode 100644 detect_file_test.go create mode 100644 detect_git_test.go create mode 100644 detect_github_test.go create mode 100644 gcs/detect_gcs_test.go create mode 100644 s3/detect_s3_test.go diff --git a/detect_bitbucket_test.go b/detect_bitbucket_test.go new file mode 100644 index 000000000..202c93256 --- /dev/null +++ b/detect_bitbucket_test.go @@ -0,0 +1,67 @@ +package getter + +import ( + "net/http" + "strings" + "testing" +) + +const testBBUrl = "https://bitbucket.org/hashicorp/tf-test-git" + +func TestBitBucketDetector(t *testing.T) { + t.Parallel() + + if _, err := http.Get(testBBUrl); err != nil { + t.Log("internet may not be working, skipping BB tests") + t.Skip() + } + + cases := []struct { + Input string + Output string + }{ + // HTTP + { + "bitbucket.org/hashicorp/tf-test-git", + "git::https://bitbucket.org/hashicorp/tf-test-git.git", + }, + { + "bitbucket.org/hashicorp/tf-test-git.git", + "git::https://bitbucket.org/hashicorp/tf-test-git.git", + }, + { + "bitbucket.org/hashicorp/tf-test-hg", + "hg::https://bitbucket.org/hashicorp/tf-test-hg", + }, + } + + pwd := "/pwd" + f := new(BitBucketDetector) + for i, tc := range cases { + var err error + for i := 0; i < 3; i++ { + var output string + var ok bool + output, ok, err = f.Detect(tc.Input, pwd) + if err != nil { + if strings.Contains(err.Error(), "invalid character") { + continue + } + + t.Fatalf("err: %s", err) + } + if !ok { + t.Fatal("not ok") + } + + if output != tc.Output { + t.Fatalf("%d: bad: %#v", i, output) + } + + break + } + if i >= 3 { + t.Fatalf("failure from bitbucket: %s", err) + } + } +} diff --git a/detect_file_test.go b/detect_file_test.go new file mode 100644 index 000000000..4b74b3200 --- /dev/null +++ b/detect_file_test.go @@ -0,0 +1,114 @@ +package getter + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" +) + +type fileTest struct { + in, pwd, out string + symlink, err bool +} + +var fileTests = []fileTest{ + {"./foo", "/pwd", "/pwd/foo", false, false}, + {"./foo?foo=bar", "/pwd", "/pwd/foo?foo=bar", false, false}, + {"foo", "/pwd", "/pwd/foo", false, false}, +} + +var unixFileTests = []fileTest{ + {"./foo", "testdata/detect-file-symlink-pwd/syml/pwd", + "testdata/detect-file-symlink-pwd/real/foo", true, false}, + + {"/foo", "/pwd", "/foo", false, false}, + {"/foo?bar=baz", "/pwd", "/foo?bar=baz", false, false}, +} + +var winFileTests = []fileTest{ + {"/foo", "/pwd", "/pwd/foo", false, false}, + {`C:\`, `/pwd`, `C:/`, false, false}, + {`C:\?bar=baz`, `/pwd`, `C:/?bar=baz`, false, false}, +} + +func TestFileDetector(t *testing.T) { + if runtime.GOOS == "windows" { + fileTests = append(fileTests, winFileTests...) + } else { + fileTests = append(fileTests, unixFileTests...) + } + + // Get the pwd + pwdRoot, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + pwdRoot, err = filepath.Abs(pwdRoot) + if err != nil { + t.Fatalf("err: %s", err) + } + + f := new(FileDetector) + for i, tc := range fileTests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + pwd := tc.pwd + + out, ok, err := f.Detect(tc.in, pwd) + if err != nil { + t.Fatalf("err: %s", err) + } + if !ok { + t.Fatal("not ok") + } + + expected := tc.out + if tc.symlink { + expected = filepath.Join(pwdRoot, expected) + } + + if out != expected { + t.Fatalf("input: %q\npwd: %q\nexpected: %q\nbad output: %#v", + tc.in, pwd, expected, out) + } + }) + } +} + +var noPwdFileTests = []fileTest{ + {in: "./foo", pwd: "", out: "", err: true}, + {in: "foo", pwd: "", out: "", err: true}, +} + +var noPwdUnixFileTests = []fileTest{ + {in: "/foo", pwd: "", out: "/foo", err: false}, +} + +var noPwdWinFileTests = []fileTest{ + {in: "/foo", pwd: "", out: "", err: true}, + {in: `C:\`, pwd: ``, out: `C:/`, err: false}, +} + +func TestFileDetector_noPwd(t *testing.T) { + if runtime.GOOS == "windows" { + noPwdFileTests = append(noPwdFileTests, noPwdWinFileTests...) + } else { + noPwdFileTests = append(noPwdFileTests, noPwdUnixFileTests...) + } + + f := new(FileDetector) + for i, tc := range noPwdFileTests { + out, ok, err := f.Detect(tc.in, tc.pwd) + if err != nil != tc.err { + t.Fatalf("%d: err: %s", i, err) + } + if !ok { + t.Fatal("not ok") + } + + if out != tc.out { + t.Fatalf("%d: bad: %#v", i, out) + } + } +} diff --git a/detect_git_test.go b/detect_git_test.go new file mode 100644 index 000000000..60463e33e --- /dev/null +++ b/detect_git_test.go @@ -0,0 +1,63 @@ +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 { + t.Run(tc.Input, func(t *testing.T) { + out, ok, err := f.Detect(tc.Input, pwd) + if err != nil { + t.Fatalf("%d: err: %s", i, err) + } + if !ok { + t.Fatal("not ok") + } + + if out != tc.Output { + t.Fatalf("%d: bad: %#v", i, out) + } + }) + } +} diff --git a/detect_github_test.go b/detect_github_test.go new file mode 100644 index 000000000..70f1c8329 --- /dev/null +++ b/detect_github_test.go @@ -0,0 +1,44 @@ +package getter + +import ( + "testing" +) + +func TestGitHubDetector(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + // HTTP + {"github.com/hashicorp/foo", "git::https://github.com/hashicorp/foo.git"}, + {"github.com/hashicorp/foo.git", "git::https://github.com/hashicorp/foo.git"}, + { + "github.com/hashicorp/foo/bar", + "git::https://github.com/hashicorp/foo.git//bar", + }, + { + "github.com/hashicorp/foo?foo=bar", + "git::https://github.com/hashicorp/foo.git?foo=bar", + }, + { + "github.com/hashicorp/foo.git?foo=bar", + "git::https://github.com/hashicorp/foo.git?foo=bar", + }, + } + + pwd := "/pwd" + f := new(GitHubDetector) + 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) + } + } +} diff --git a/gcs/detect_gcs_test.go b/gcs/detect_gcs_test.go new file mode 100644 index 000000000..9a9a3e280 --- /dev/null +++ b/gcs/detect_gcs_test.go @@ -0,0 +1,41 @@ +package gcs + +import ( + "testing" +) + +func TestGCSDetector(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + { + "www.googleapis.com/storage/v1/bucket/foo", + "https://www.googleapis.com/storage/v1/bucket/foo", + }, + { + "www.googleapis.com/storage/v1/bucket/foo/bar", + "https://www.googleapis.com/storage/v1/bucket/foo/bar", + }, + { + "www.googleapis.com/storage/v1/foo/bar.baz", + "https://www.googleapis.com/storage/v1/foo/bar.baz", + }, + } + + pwd := "/pwd" + f := new(Detector) + 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) + } + } +} diff --git a/gcs/get_gcs_test.go b/gcs/get_gcs_test.go index 31f860a4e..d504b4458 100644 --- a/gcs/get_gcs_test.go +++ b/gcs/get_gcs_test.go @@ -237,44 +237,4 @@ func TestGetter_Url(t *testing.T) { } }) } -} - -func TestGetter_Detect(t *testing.T) { - cases := []struct { - Input string - Output string - }{ - { - "www.googleapis.com/storage/v1/bucket/foo", - "https://www.googleapis.com/storage/v1/bucket/foo", - }, - { - "www.googleapis.com/storage/v1/bucket/foo/bar", - "https://www.googleapis.com/storage/v1/bucket/foo/bar", - }, - { - "www.googleapis.com/storage/v1/foo/bar.baz", - "https://www.googleapis.com/storage/v1/foo/bar.baz", - }, - } - - pwd := "/pwd" - f := new(Getter) - for i, tc := range cases { - req := &getter.Request{ - Src: tc.Input, - Pwd: pwd, - } - ok, err := getter.Detect(req, f) - if err != nil { - t.Fatalf("err: %s", err) - } - if !ok { - t.Fatal("not ok") - } - - if req.Src != tc.Output { - t.Fatalf("%d: bad: %#v", i, req.Src) - } - } -} +} \ No newline at end of file diff --git a/get_file.go b/get_file.go index 6c1e2ded1..1e7262315 100644 --- a/get_file.go +++ b/get_file.go @@ -11,7 +11,7 @@ import ( // FileGetter is a Getter implementation that will download a module from // a file scheme. -type FileGetter struct {} +type FileGetter struct{} func (g *FileGetter) Mode(ctx context.Context, u *url.URL) (Mode, error) { path := u.Path diff --git a/get_file_test.go b/get_file_test.go index e485c12a6..71e05f7c7 100644 --- a/get_file_test.go +++ b/get_file_test.go @@ -2,10 +2,8 @@ package getter import ( "context" - "fmt" "os" "path/filepath" - "runtime" "testing" testing_helper "github.com/hashicorp/go-getter/v2/helper/testing" @@ -259,116 +257,3 @@ func TestFileGetter_Mode_dir(t *testing.T) { t.Fatal("expect ModeDir") } } - -type fileTest struct { - in, pwd, out string - symlink, err bool -} - -var fileTests = []fileTest{ - {"./foo", "/pwd", "/pwd/foo", false, false}, - {"./foo?foo=bar", "/pwd", "/pwd/foo?foo=bar", false, false}, - {"foo", "/pwd", "/pwd/foo", false, false}, -} - -var unixFileTests = []fileTest{ - {"./foo", "testdata/detect-file-symlink-pwd/syml/pwd", - "testdata/detect-file-symlink-pwd/real/foo", true, false}, - - {"/foo", "/pwd", "/foo", false, false}, - {"/foo?bar=baz", "/pwd", "/foo?bar=baz", false, false}, -} - -var winFileTests = []fileTest{ - {"/foo", "/pwd", "/pwd/foo", false, false}, - {`C:\`, `/pwd`, `C:/`, false, false}, - {`C:\?bar=baz`, `/pwd`, `C:/?bar=baz`, false, false}, -} - -func TestFileDetector(t *testing.T) { - if runtime.GOOS == "windows" { - fileTests = append(fileTests, winFileTests...) - } else { - fileTests = append(fileTests, unixFileTests...) - } - - // Get the pwd - pwdRoot, err := os.Getwd() - if err != nil { - t.Fatalf("err: %s", err) - } - pwdRoot, err = filepath.Abs(pwdRoot) - if err != nil { - t.Fatalf("err: %s", err) - } - - f := new(FileGetter) - for i, tc := range fileTests { - t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - pwd := tc.pwd - - req := &Request{ - Src: tc.in, - Pwd: pwd, - } - ok, err := Detect(req, f) - if err != nil { - t.Fatalf("err: %s", err) - } - if !ok { - t.Fatal("not ok") - } - - expected := tc.out - if tc.symlink { - expected = filepath.Join(pwdRoot, expected) - } - - if req.Src != expected { - t.Fatalf("input: %q\npwd: %q\nexpected: %q\nbad output: %#v", - tc.in, pwd, expected, req.Src) - } - }) - } -} - -var noPwdFileTests = []fileTest{ - {in: "./foo", pwd: "", out: "./foo", err: true}, - {in: "foo", pwd: "", out: "foo", err: true}, -} - -var noPwdUnixFileTests = []fileTest{ - {in: "/foo", pwd: "", out: "/foo", err: false}, -} - -var noPwdWinFileTests = []fileTest{ - {in: "/foo", pwd: "", out: "/foo", err: true}, - {in: `C:\`, pwd: ``, out: `C:/`, err: false}, -} - -func TestFileDetector_noPwd(t *testing.T) { - if runtime.GOOS == "windows" { - noPwdFileTests = append(noPwdFileTests, noPwdWinFileTests...) - } else { - noPwdFileTests = append(noPwdFileTests, noPwdUnixFileTests...) - } - - f := new(FileGetter) - for i, tc := range noPwdFileTests { - req := &Request{ - Src: tc.in, - Pwd: tc.pwd, - } - ok, err := Detect(req, f) - if err != nil != tc.err { - t.Fatalf("%d: err: %s", i, err) - } - if !ok { - t.Fatal("not ok") - } - - if req.Src != tc.out { - t.Fatalf("%d: bad: %#v", i, req.Src) - } - } -} diff --git a/get_git_test.go b/get_git_test.go index 2b0275685..7b9e7c8ee 100644 --- a/get_git_test.go +++ b/get_git_test.go @@ -697,6 +697,10 @@ func TestGitGetter_Detector(t *testing.T) { "bitbucket.org/hashicorp/tf-test-git.git", "https://bitbucket.org/hashicorp/tf-test-git.git", }, + { + "git::ssh://git@git.example.com:2222/hashicorp/foo.git", + "ssh://git@git.example.com:2222/hashicorp/foo.git", + }, } pwd := "/pwd" diff --git a/get_hg.go b/get_hg.go index 7d6f5a54f..7d683b6ad 100644 --- a/get_hg.go +++ b/get_hg.go @@ -15,9 +15,7 @@ import ( // HgGetter is a Getter implementation that will download a module from // a Mercurial repository. -type HgGetter struct { - Detectors []Detector -} +type HgGetter struct {} func (g *HgGetter) Mode(ctx context.Context, _ *url.URL) (Mode, error) { return ModeDir, nil diff --git a/get_hg_test.go b/get_hg_test.go index e207cfea8..a9b9f9bc7 100644 --- a/get_hg_test.go +++ b/get_hg_test.go @@ -121,8 +121,6 @@ func TestHgGetter_GetFile(t *testing.T) { testing_helper.AssertContents(t, dst, "Hello\n") } -const testBBUrl = "https://bitbucket.org/hashicorp/tf-test-git" - func TestHgGetter_DetectBitBucketDetector(t *testing.T) { t.Parallel() diff --git a/s3/detect_s3_test.go b/s3/detect_s3_test.go new file mode 100644 index 000000000..4b9cde48e --- /dev/null +++ b/s3/detect_s3_test.go @@ -0,0 +1,84 @@ +package s3 + +import ( + "testing" +) + +func TestS3Detector(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + // Virtual hosted style + { + "bucket.s3.amazonaws.com/foo", + "https://s3.amazonaws.com/bucket/foo", + }, + { + "bucket.s3.amazonaws.com/foo/bar", + "https://s3.amazonaws.com/bucket/foo/bar", + }, + { + "bucket.s3.amazonaws.com/foo/bar.baz", + "https://s3.amazonaws.com/bucket/foo/bar.baz", + }, + { + "bucket.s3-eu-west-1.amazonaws.com/foo", + "https://s3-eu-west-1.amazonaws.com/bucket/foo", + }, + { + "bucket.s3-eu-west-1.amazonaws.com/foo/bar", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", + }, + { + "bucket.s3-eu-west-1.amazonaws.com/foo/bar.baz", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", + }, + // Path style + { + "s3.amazonaws.com/bucket/foo", + "https://s3.amazonaws.com/bucket/foo", + }, + { + "s3.amazonaws.com/bucket/foo/bar", + "https://s3.amazonaws.com/bucket/foo/bar", + }, + { + "s3.amazonaws.com/bucket/foo/bar.baz", + "https://s3.amazonaws.com/bucket/foo/bar.baz", + }, + { + "s3-eu-west-1.amazonaws.com/bucket/foo", + "https://s3-eu-west-1.amazonaws.com/bucket/foo", + }, + { + "s3-eu-west-1.amazonaws.com/bucket/foo/bar", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", + }, + { + "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", + }, + // Misc tests + { + "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", + "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", + }, + } + + pwd := "/pwd" + f := new(Detector) + 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) + } + } +} diff --git a/s3/get_s3_test.go b/s3/get_s3_test.go index 271d49663..4de2a19af 100644 --- a/s3/get_s3_test.go +++ b/s3/get_s3_test.go @@ -311,87 +311,4 @@ func TestGetter_Url(t *testing.T) { } }) } -} - -func TestGetter_Detect(t *testing.T) { - cases := []struct { - Input string - Output string - }{ - // Virtual hosted style - { - "bucket.s3.amazonaws.com/foo", - "https://s3.amazonaws.com/bucket/foo", - }, - { - "bucket.s3.amazonaws.com/foo/bar", - "https://s3.amazonaws.com/bucket/foo/bar", - }, - { - "bucket.s3.amazonaws.com/foo/bar.baz", - "https://s3.amazonaws.com/bucket/foo/bar.baz", - }, - { - "bucket.s3-eu-west-1.amazonaws.com/foo", - "https://s3-eu-west-1.amazonaws.com/bucket/foo", - }, - { - "bucket.s3-eu-west-1.amazonaws.com/foo/bar", - "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", - }, - { - "bucket.s3-eu-west-1.amazonaws.com/foo/bar.baz", - "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", - }, - // Path style - { - "s3.amazonaws.com/bucket/foo", - "https://s3.amazonaws.com/bucket/foo", - }, - { - "s3.amazonaws.com/bucket/foo/bar", - "https://s3.amazonaws.com/bucket/foo/bar", - }, - { - "s3.amazonaws.com/bucket/foo/bar.baz", - "https://s3.amazonaws.com/bucket/foo/bar.baz", - }, - { - "s3-eu-west-1.amazonaws.com/bucket/foo", - "https://s3-eu-west-1.amazonaws.com/bucket/foo", - }, - { - "s3-eu-west-1.amazonaws.com/bucket/foo/bar", - "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", - }, - { - "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", - "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", - }, - // Misc tests - { - "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", - "https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", - }, - } - - pwd := "/pwd" - f := new(Getter) - for i, tc := range cases { - req := &getter.Request{ - Src: tc.Input, - Pwd: pwd, - } - ok, err := getter.Detect(req, f) - if err != nil { - t.Fatalf("err: %s", err) - } - if !ok { - t.Fatal("not ok") - } - - if req.Src != tc.Output { - t.Fatalf("%d: bad: %#v", i, req.Src) - } - } -} +} \ No newline at end of file From 6e2f08ed804a297279c225a98c9b539432fc6780 Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 2 Jun 2020 11:54:55 +0200 Subject: [PATCH 108/109] Make SmbMountGetter MacOS and Windows specific --- README.md | 12 +++++------- get_hg.go | 2 +- get_smb_mount.go | 9 ++++----- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0962a161e..151b56bb4 100644 --- a/README.md +++ b/README.md @@ -369,25 +369,23 @@ There are two options that go-getter will use to download a file in a smb shared to look for a file in a local mount of the shared folder in the OS specific volume folder. go-getter will try to download files from a smb shared folder whenever the url is prefixed with `smb://`. -⚠️ The [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) command is available only for Unix. -This is the best option for a Unix user and therefore the client must be installed. +⚠️ The [`smbclient`](https://www.samba.org/samba/docs/current/man-html/smbclient.1.html) command is available only for Linux. +This is the ONLY option for a Linux user and therefore the client must be installed. -The `smbclient` cli is not available for Windows and MacOS. Whenever the `smbclient` is not available, go-getter -will try to get files using the file system, when this happens the getter uses the FileGetter implementation. -⚠️ The FileGetter need to be enabled for this to work on Windows and MacOS. +The `smbclient` cli is not available for Windows and MacOS. The go-getter +will try to get files using the file system, when this happens the getter uses the FileGetter implementation. When connecting to a smb server, the OS creates a local mount in a system specific volume folder, and go-getter will try to access the following folders when looking for local mounts. - MacOS: /Volumes/ - Windows: \\\\\\\\ -- Unix: /run/user/1000/gvfs/smb-share:server=\,share= The following examples work for all the OSes: - smb://host/shared/dir (downloads directory content) - smb://host/shared/dir/file (downloads file) -The following examples work for Unix: +The following examples work for Linux: - smb://username:password@host/shared/dir (downloads directory content) - smb://username@host/shared/dir - smb://username:password@host/shared/dir/file (downloads file) diff --git a/get_hg.go b/get_hg.go index 7d683b6ad..8cc4fe4c6 100644 --- a/get_hg.go +++ b/get_hg.go @@ -15,7 +15,7 @@ import ( // HgGetter is a Getter implementation that will download a module from // a Mercurial repository. -type HgGetter struct {} +type HgGetter struct{} func (g *HgGetter) Mode(ctx context.Context, _ *url.URL) (Mode, error) { return ModeDir, nil diff --git a/get_smb_mount.go b/get_smb_mount.go index 6c27e253a..37927e6bc 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -2,12 +2,10 @@ package getter import ( "context" - "fmt" "net/url" "os" "path/filepath" "runtime" - "strings" ) // SmbMountGetter is a Getter implementation that will download a module from @@ -59,14 +57,15 @@ func (g *SmbMountGetter) findPrefixAndPath(u *url.URL) (string, string) { case "darwin": prefix = string(os.PathSeparator) path = filepath.Join("Volumes", u.Path) - case "linux": - prefix = string(os.PathSeparator) - path = fmt.Sprintf("run/user/1000/gvfs/smb-share:server=%s,share=%s", u.Host, strings.TrimPrefix(u.Path, prefix)) } return prefix, path } func (g *SmbMountGetter) Detect(req *Request) (bool, error) { + if runtime.GOOS == "linux" { + // Linux users should use smbclient command. + return false, nil + } if len(req.Src) == 0 { return false, nil } From 1ab379be7fbd22fdf779c42c5ea69fc4a63f594b Mon Sep 17 00:00:00 2001 From: Moss Date: Tue, 2 Jun 2020 16:44:15 +0200 Subject: [PATCH 109/109] Explain better the code in comments --- client.go | 69 ++++++++++++++++++++++++++++++------------------ get_smb_mount.go | 5 ++-- request.go | 15 +++++++++-- s3/get_s3.go | 3 +-- 4 files changed, 61 insertions(+), 31 deletions(-) diff --git a/client.go b/client.go index 153b81607..2679fd6ff 100644 --- a/client.go +++ b/client.go @@ -62,20 +62,21 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { var multierr []error for _, g := range c.Getters { - ok, err := Detect(req, g) + shouldDownload, err := Detect(req, g) if err != nil { return nil, err } - if !ok { + if !shouldDownload { + // the request should not be processed by that getter continue } - result, fatal, err := c.get(ctx, req, g) - if err != nil { - if fatal { - return nil, err + result, getErr := c.get(ctx, req, g) + if getErr != nil { + if getErr.Fatal { + return nil, getErr.Err } - multierr = append(multierr, err) + multierr = append(multierr, getErr.Err) continue } @@ -96,11 +97,24 @@ func (c *Client) Get(ctx context.Context, req *Request) (*GetResult, error) { return nil, fmt.Errorf("error downloading '%s'", req.Src) } -func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, bool, error) { +// getError is the Error response object returned by get(context.Context, *Request, Getter) +// to tell the client whether to halt (Fatal) Get or to keep trying to get an artifact. +type getError struct { + // When Fatal is true something went wrong with get(context.Context, *Request, Getter) + // and the client should halt and return the Err. + Fatal bool + Err error +} + +func (ge *getError) Error() string { + return ge.Err.Error() +} + +func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, *getError) { u, err := urlhelper.Parse(req.Src) req.u = u if err != nil { - return nil, true, err + return nil, &getError{true, err} } // We have magic query parameters that we use to signal different features @@ -141,8 +155,8 @@ func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, b // this at the end of everything. td, err := ioutil.TempDir("", "getter") if err != nil { - return nil, true, fmt.Errorf( - "Error creating temporary directory for archive: %s", err) + return nil, &getError{true, fmt.Errorf( + "Error creating temporary directory for archive: %s", err)} } defer os.RemoveAll(td) @@ -157,7 +171,7 @@ func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, b // Determine checksum if we have one checksum, err := c.GetChecksum(ctx, req) if err != nil { - return nil, true, fmt.Errorf("invalid checksum: %s", err) + return nil, &getError{true, fmt.Errorf("invalid checksum: %s", err)} } // Delete the query parameter if we have it. @@ -168,7 +182,7 @@ func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, b // Ask the getter which client mode to use req.Mode, err = g.Mode(ctx, req.u) if err != nil { - return nil, false, err + return nil, &getError{false, err} } // Destination is the base name of the URL path in "any" mode when @@ -201,12 +215,12 @@ func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, b } if getFile { if err := g.GetFile(ctx, req); err != nil { - return nil, false, err + return nil, &getError{false, err} } if checksum != nil { if err := checksum.Checksum(req.Dst); err != nil { - return nil, true, err + return nil, &getError{true, err} } } } @@ -216,7 +230,7 @@ func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, b // into the final destination with the proper mode. err := decompressor.Decompress(decompressDst, req.Dst, decompressDir) if err != nil { - return nil, true, err + return nil, &getError{true, err} } // Swap the information back @@ -232,7 +246,7 @@ func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, b // if we were unarchiving. If we're still only Get-ing a file, then // we're done. if req.Mode == ModeFile { - return &GetResult{req.Dst}, false, nil + return &GetResult{req.Dst}, nil } } @@ -244,35 +258,40 @@ func (c *Client) get(ctx context.Context, req *Request, g Getter) (*GetResult, b // If we're getting a directory, then this is an error. You cannot // checksum a directory. TODO: test if checksum != nil { - return nil, true, fmt.Errorf( - "checksum cannot be specified for directory download") + return nil, &getError{true, fmt.Errorf( + "checksum cannot be specified for directory download")} } // We're downloading a directory, which might require a bit more work // if we're specifying a subdir. if err := g.Get(ctx, req); err != nil { - return nil, false, err + return nil, &getError{false, err} } } // If we have a subdir, copy that over if req.subDir != "" { if err := os.RemoveAll(req.realDst); err != nil { - return nil, true, err + return nil, &getError{true, err} } if err := os.MkdirAll(req.realDst, 0755); err != nil { - return nil, true, err + return nil, &getError{true, err} } // Process any globs subDir, err := SubdirGlob(req.Dst, req.subDir) if err != nil { - return nil, true, err + return nil, &getError{true, err} } - return &GetResult{req.realDst}, false, copyDir(ctx, req.realDst, subDir, false) + err = copyDir(ctx, req.realDst, subDir, false) + if err != nil { + return nil, &getError{false, err} + } + return &GetResult{req.realDst}, nil } - return &GetResult{req.Dst}, false, nil + + return &GetResult{req.Dst}, nil } diff --git a/get_smb_mount.go b/get_smb_mount.go index 37927e6bc..3e55cfd8c 100644 --- a/get_smb_mount.go +++ b/get_smb_mount.go @@ -8,7 +8,7 @@ import ( "runtime" ) -// SmbMountGetter is a Getter implementation that will download a module from +// SmbMountGetter is a Getter implementation that will download an artifact from // a shared folder using the file system using FileGetter implementation. // For Unix and MacOS users, the Getter will look for usual system specific mount paths such as: // /Volumes/ for MacOS @@ -63,7 +63,8 @@ func (g *SmbMountGetter) findPrefixAndPath(u *url.URL) (string, string) { func (g *SmbMountGetter) Detect(req *Request) (bool, error) { if runtime.GOOS == "linux" { - // Linux users should use smbclient command. + // Linux has the smbclient command which is a safer approach to retrieve an artifact from a samba shared folder. + // Therefore, this should be used instead of looking in the file system. return false, nil } if len(req.Src) == 0 { diff --git a/request.go b/request.go index df3224192..5b4362696 100644 --- a/request.go +++ b/request.go @@ -18,8 +18,19 @@ type Request struct { Dst string Pwd string - // Forced getter detected in Src during Getter detection phase - // This is currently for internal use only + // Forced is the forced getter detected in the Src string during the + // Getter detection. Forcing a getter means that go-getter will try + // to download the artifact only with the Getter that is being forced. + // + // For example: + // + // Request.Src Forced + // git::ssh://git@git.example.com:2222/foo/bar.git git + // + // This field is used by the Getters to validate when they are forced to download + // the artifact. + // If both Request.Src and Forced contains a forced getter, the one in the Request.Src will + // be considered and will override the value of this field. Forced string // Mode is the method of download the client will use. See Mode diff --git a/s3/get_s3.go b/s3/get_s3.go index d65ad71e2..6696c9b00 100644 --- a/s3/get_s3.go +++ b/s3/get_s3.go @@ -307,10 +307,9 @@ func (g *Getter) Detect(req *getter.Request) (bool, error) { } if ok { req.Src = src - return ok, nil } - return false, nil + return ok, nil } func (g *Getter) validScheme(scheme string) bool {