From 00144a6ef1d206e669522c6e079c552d01446c82 Mon Sep 17 00:00:00 2001
From: Will Roden <willabides@github.com>
Date: Sat, 23 Nov 2019 13:01:05 -0600
Subject: [PATCH 1/3] add ArchivePath and Link to download config

---
 downloader.go                           |  75 ++++++++--------
 downloader_test.go                      | 109 ++++++++++++++++++++++--
 testdata/downloadables/fooinroot.tar.gz | Bin 0 -> 179 bytes
 testdata/downloadables/rawfile/foo      |   3 +
 testhelper_test.go                      |   4 +-
 5 files changed, 142 insertions(+), 49 deletions(-)
 create mode 100644 testdata/downloadables/fooinroot.tar.gz
 create mode 100644 testdata/downloadables/rawfile/foo

diff --git a/downloader.go b/downloader.go
index 15e002dd..8edad8ba 100644
--- a/downloader.go
+++ b/downloader.go
@@ -16,13 +16,15 @@ import (
 
 // Downloader downloads a binary
 type Downloader struct {
-	URL        string `json:"url"`
-	Checksum   string `json:"checksum"`
-	LinkSource string `json:"symlink,omitempty"`
-	BinName    string `json:"bin"`
-	MoveFrom   string `json:"move-from"`
-	OS         string `json:"os"`
-	Arch       string `json:"arch"`
+	URL         string `json:"url"`
+	Checksum    string `json:"checksum"`
+	LinkSource  string `json:"symlink,omitempty"`
+	BinName     string `json:"bin"`
+	MoveFrom    string `json:"move-from"`
+	ArchivePath string `json:"archive_path"`
+	Link        bool   `json:"link"`
+	OS          string `json:"os"`
+	Arch        string `json:"arch"`
 }
 
 func (d *Downloader) downloadableName() (string, error) {
@@ -49,45 +51,46 @@ func (d *Downloader) chmod(targetDir string) error {
 	return os.Chmod(d.binPath(targetDir), 0755) //nolint:gosec
 }
 
-func (d *Downloader) move(targetDir, extractDir string) error {
-	if d.MoveFrom == "" {
-		return nil
+func (d *Downloader) moveOrLinkBin(targetDir, extractDir string) error {
+	if d.LinkSource != "" {
+		d.ArchivePath = d.LinkSource
+		d.Link = true
 	}
-	err := rm(d.binPath(targetDir))
-	if err != nil {
-		return err
+	if d.MoveFrom != "" {
+		d.ArchivePath = d.MoveFrom
 	}
-	from := filepath.Join(extractDir, filepath.FromSlash(d.MoveFrom))
-	to := d.binPath(targetDir)
-	return os.Rename(from, to)
-}
-
-func (d *Downloader) link(targetDir, extractDir string) error {
-	if d.LinkSource == "" {
-		return nil
+	archivePath := filepath.FromSlash(d.ArchivePath)
+	if archivePath == "" {
+		archivePath = filepath.FromSlash(d.BinName)
 	}
 	var err error
-	if fileExists(d.binPath(targetDir)) {
-		err = rm(d.binPath(targetDir))
+	target := d.binPath(targetDir)
+	if fileExists(target) {
+		err = rm(target)
 		if err != nil {
 			return err
 		}
 	}
+
 	extractDir, err = filepath.Abs(extractDir)
 	if err != nil {
 		return err
 	}
-	target := d.binPath(targetDir)
-	targetDir, err = filepath.Abs(filepath.Dir(target))
-	if err != nil {
-		return err
-	}
+	extractedBin := filepath.Join(extractDir, archivePath)
 
-	dst, err := filepath.Rel(targetDir, filepath.Join(extractDir, filepath.FromSlash(d.LinkSource)))
-	if err != nil {
-		return err
+	if d.Link {
+		targetDir, err = filepath.Abs(filepath.Dir(target))
+		if err != nil {
+			return err
+		}
+
+		dst, err := filepath.Rel(targetDir, extractedBin)
+		if err != nil {
+			return err
+		}
+		return os.Symlink(dst, target)
 	}
-	return os.Symlink(dst, d.binPath(targetDir))
+	return os.Rename(extractedBin, target)
 }
 
 func (d *Downloader) extract(downloadDir, extractDir string) error {
@@ -206,13 +209,7 @@ func (d *Downloader) Install(opts InstallOpts) error {
 		return err
 	}
 
-	err = d.link(opts.TargetDir, extractDir)
-	if err != nil {
-		log.Printf("error linking: %v", err)
-		return err
-	}
-
-	err = d.move(opts.TargetDir, extractDir)
+	err = d.moveOrLinkBin(opts.TargetDir, extractDir)
 	if err != nil {
 		log.Printf("error moving: %v", err)
 		return err
diff --git a/downloader_test.go b/downloader_test.go
index 23c408b4..599623ce 100644
--- a/downloader_test.go
+++ b/downloader_test.go
@@ -19,16 +19,16 @@ func Test_downloadFile(t *testing.T) {
 	t.Run("success", func(t *testing.T) {
 		dir, teardown := tmpDir(t)
 		defer teardown()
-		ts := serveFile(fooPath, "/foo/foo.tar.gz", "")
+		ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "")
 		err := downloadFile(filepath.Join(dir, "bar.tar.gz"), ts.URL+"/foo/foo.tar.gz")
 		assert.NoError(t, err)
-		assertEqualFiles(t, fooPath, filepath.Join(dir, "bar.tar.gz"))
+		assertEqualFiles(t, downloadablesPath("foo.tar.gz"), filepath.Join(dir, "bar.tar.gz"))
 	})
 
 	t.Run("404", func(t *testing.T) {
 		dir, teardown := tmpDir(t)
 		defer teardown()
-		ts := serveFile(fooPath, "/foo/foo.tar.gz", "")
+		ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "")
 		err := downloadFile(filepath.Join(dir, "bar.tar.gz"), ts.URL+"/wrongpath")
 		assert.Error(t, err)
 	})
@@ -43,7 +43,7 @@ func Test_downloadFile(t *testing.T) {
 	t.Run("bad target", func(t *testing.T) {
 		dir, teardown := tmpDir(t)
 		defer teardown()
-		ts := serveFile(fooPath, "/foo/foo.tar.gz", "")
+		ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "")
 		err := downloadFile(filepath.Join(dir, "notreal", "bar.tar.gz"), ts.URL+"/foo/foo.tar.gz")
 		assert.Error(t, err)
 	})
@@ -57,7 +57,7 @@ func Test_downloader_validateChecksum(t *testing.T) {
 			URL:      "foo/foo.tar.gz",
 			Checksum: "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88",
 		}
-		mustCopyFile(t, fooPath, filepath.Join(dir, "foo.tar.gz"))
+		mustCopyFile(t, downloadablesPath("foo.tar.gz"), filepath.Join(dir, "foo.tar.gz"))
 		err := d.validateChecksum(dir)
 		assert.NoError(t, err)
 		assert.True(t, fileExists(filepath.Join(dir, "foo.tar.gz")))
@@ -82,7 +82,7 @@ func Test_downloader_validateChecksum(t *testing.T) {
 			URL:      "foo/foo.tar.gz",
 			Checksum: "deadbeef",
 		}
-		mustCopyFile(t, fooPath, filepath.Join(dir, "foo.tar.gz"))
+		mustCopyFile(t, downloadablesPath("foo.tar.gz"), filepath.Join(dir, "foo.tar.gz"))
 		err := d.validateChecksum(dir)
 		assert.Error(t, err)
 		assert.False(t, fileExists(filepath.Join(dir, "foo.tar.gz")))
@@ -98,16 +98,83 @@ func TestDownloader_extract(t *testing.T) {
 	}
 	downloadDir := filepath.Join(dir, "download")
 	extractDir := filepath.Join(dir, "extract")
-	mustCopyFile(t, fooPath, filepath.Join(downloadDir, "foo.tar.gz"))
+	mustCopyFile(t, downloadablesPath("foo.tar.gz"), filepath.Join(downloadDir, "foo.tar.gz"))
 	err := d.extract(downloadDir, extractDir)
 	assert.NoError(t, err)
 }
 
 func TestDownloader_Install(t *testing.T) {
+	t.Run("raw file", func(t *testing.T) {
+		dir, teardown := tmpDir(t)
+		defer teardown()
+		servePath := downloadablesPath("rawfile/foo")
+		ts := serveFile(servePath, "/foo/foo", "")
+		d := &Downloader{
+			URL:      ts.URL + "/foo/foo",
+			Checksum: "f044ff8b6007c74bcc1b5a5c92776e5d49d6014f5ff2d551fab115c17f48ac41",
+			BinName:  "foo",
+			Arch:     "amd64",
+			OS:       "darwin",
+		}
+		err := d.Install(InstallOpts{
+			TargetDir: dir,
+			Force:     true,
+		})
+		assert.NoError(t, err)
+		assert.True(t, fileExists(filepath.Join(dir, "foo")))
+		stat, err := os.Stat(filepath.Join(dir, "foo"))
+		assert.NoError(t, err)
+		assert.False(t, stat.IsDir())
+		assert.Equal(t, os.FileMode(0755), stat.Mode().Perm())
+	})
+
+	t.Run("bin in root", func(t *testing.T) {
+		dir, teardown := tmpDir(t)
+		defer teardown()
+		servePath := downloadablesPath("fooinroot.tar.gz")
+		ts := serveFile(servePath, "/foo/fooinroot.tar.gz", "")
+		d := &Downloader{
+			URL:      ts.URL + "/foo/fooinroot.tar.gz",
+			Checksum: "27dcce60d1ed72920a84dd4bc01e0bbd013e5a841660e9ee2e964e53fb83c0b3",
+			BinName:  "foo",
+			Arch:     "amd64",
+			OS:       "darwin",
+		}
+		err := d.Install(InstallOpts{
+			TargetDir: dir,
+			Force:     true,
+		})
+		assert.NoError(t, err)
+		assert.True(t, fileExists(filepath.Join(dir, "foo")))
+		stat, err := os.Stat(filepath.Join(dir, "foo"))
+		assert.NoError(t, err)
+		assert.False(t, stat.IsDir())
+		assert.Equal(t, os.FileMode(0755), stat.Mode().Perm())
+	})
+
 	t.Run("move", func(t *testing.T) {
 		dir, teardown := tmpDir(t)
 		defer teardown()
-		ts := serveFile(fooPath, "/foo/foo.tar.gz", "foo=bar")
+		ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar")
+		d := &Downloader{
+			URL:         ts.URL + "/foo/foo.tar.gz?foo=bar",
+			Checksum:    "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88",
+			BinName:     "foo.txt",
+			ArchivePath: "bin/foo.txt",
+			Arch:        "amd64",
+			OS:          "darwin",
+		}
+		err := d.Install(InstallOpts{
+			TargetDir: dir,
+			Force:     true,
+		})
+		assert.NoError(t, err)
+	})
+
+	t.Run("legacy MoveFrom", func(t *testing.T) {
+		dir, teardown := tmpDir(t)
+		defer teardown()
+		ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar")
 		d := &Downloader{
 			URL:      ts.URL + "/foo/foo.tar.gz?foo=bar",
 			Checksum: "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88",
@@ -126,7 +193,7 @@ func TestDownloader_Install(t *testing.T) {
 	t.Run("link", func(t *testing.T) {
 		dir, teardown := tmpDir(t)
 		defer teardown()
-		ts := serveFile(fooPath, "/foo/foo.tar.gz", "foo=bar")
+		ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar")
 		d := &Downloader{
 			URL:        ts.URL + "/foo/foo.tar.gz?foo=bar",
 			Checksum:   "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88",
@@ -145,4 +212,28 @@ func TestDownloader_Install(t *testing.T) {
 		absLinkTo := filepath.Join(dir, linksTo)
 		assert.True(t, fileExists(absLinkTo))
 	})
+
+	t.Run("legacy LinkSource", func(t *testing.T) {
+		dir, teardown := tmpDir(t)
+		defer teardown()
+		ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar")
+		d := &Downloader{
+			URL:         ts.URL + "/foo/foo.tar.gz?foo=bar",
+			Checksum:    "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88",
+			BinName:     "foo",
+			ArchivePath: "bin/foo.txt",
+			Link:        true,
+			Arch:        "amd64",
+			OS:          "darwin",
+		}
+		err := d.Install(InstallOpts{
+			TargetDir: dir,
+			Force:     true,
+		})
+		assert.NoError(t, err)
+		linksTo, err := os.Readlink(filepath.Join(dir, "foo"))
+		assert.NoError(t, err)
+		absLinkTo := filepath.Join(dir, linksTo)
+		assert.True(t, fileExists(absLinkTo))
+	})
 }
diff --git a/testdata/downloadables/fooinroot.tar.gz b/testdata/downloadables/fooinroot.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..d0ed24cfac337336bd8843a7cfb12e5887743211
GIT binary patch
literal 179
zcmb2|=3roLxEag9{Pv<Z*C7Lu)`va2yE=+LXUM(I+`80HDnoR8enz)(_hf}f|9$xy
zC(L;=Z;FOrw(Q>N-fgB^OSAKqtgPiQJN9Dhm18XuEnl9l<}93_e|E}hn`I?0kA46B
zY1_X0mg&{Uwd;>~e$^D+r~Kc&ATRx2<;Ju3U9&&^$(7AkEPWW)Z9o72#|5`uuXwg_
e)vnJIQf~WP-EPgufCPS=5e}%^pvIuVzyJUim{wx|

literal 0
HcmV?d00001

diff --git a/testdata/downloadables/rawfile/foo b/testdata/downloadables/rawfile/foo
new file mode 100644
index 00000000..f57cf482
--- /dev/null
+++ b/testdata/downloadables/rawfile/foo
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo bar
diff --git a/testhelper_test.go b/testhelper_test.go
index d6d73bd8..da0c8e15 100644
--- a/testhelper_test.go
+++ b/testhelper_test.go
@@ -14,7 +14,9 @@ import (
 	"github.com/udhos/equalfile"
 )
 
-var fooPath = projectPath("testdata", "downloadables", "foo.tar.gz")
+func downloadablesPath(path string) string {
+	return filepath.Join(projectPath("testdata", "downloadables"), filepath.FromSlash(path))
+}
 
 // projectPath exchanges a path relative to the project root for an absolute path
 func projectPath(path ...string) string {

From 232f4294c8b3d5a8a9c9ab23be05cae99ca73f75 Mon Sep 17 00:00:00 2001
From: Will Roden <willabides@github.com>
Date: Sat, 23 Nov 2019 13:11:16 -0600
Subject: [PATCH 2/3] mark MoveFrom and LinkSource deprecated

---
 downloader.go | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/downloader.go b/downloader.go
index 8edad8ba..75774703 100644
--- a/downloader.go
+++ b/downloader.go
@@ -18,13 +18,17 @@ import (
 type Downloader struct {
 	URL         string `json:"url"`
 	Checksum    string `json:"checksum"`
-	LinkSource  string `json:"symlink,omitempty"`
 	BinName     string `json:"bin"`
-	MoveFrom    string `json:"move-from"`
 	ArchivePath string `json:"archive_path"`
 	Link        bool   `json:"link"`
 	OS          string `json:"os"`
 	Arch        string `json:"arch"`
+
+	// Deprecated: use ArchivePath
+	MoveFrom string `json:"move-from"`
+
+	// Deprecated: use ArchivePath and Link
+	LinkSource string `json:"symlink,omitempty"`
 }
 
 func (d *Downloader) downloadableName() (string, error) {
@@ -52,10 +56,12 @@ func (d *Downloader) chmod(targetDir string) error {
 }
 
 func (d *Downloader) moveOrLinkBin(targetDir, extractDir string) error {
+	//noinspection GoDeprecation
 	if d.LinkSource != "" {
 		d.ArchivePath = d.LinkSource
 		d.Link = true
 	}
+	//noinspection GoDeprecation
 	if d.MoveFrom != "" {
 		d.ArchivePath = d.MoveFrom
 	}

From 66bb90a998dd6aec71bead6e16fb909b86bcf796 Mon Sep 17 00:00:00 2001
From: Will Roden <willabides@github.com>
Date: Sat, 23 Nov 2019 13:20:17 -0600
Subject: [PATCH 3/3] update buildtools.json to not use deprecated fields

---
 buildtools.json | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/buildtools.json b/buildtools.json
index 36a26b0f..832d8bc1 100644
--- a/buildtools.json
+++ b/buildtools.json
@@ -5,7 +5,7 @@
       "arch": "amd64",
       "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/darwin-amd64",
       "checksum": "84ed966949e06bebd7d006bc343caf9d736932fd8b37df5cb5b268a28d07bd30",
-      "move-from": "darwin-amd64",
+      "archive_path": "darwin-amd64",
       "bin": "gobin"
     },
     {
@@ -13,7 +13,7 @@
       "arch": "amd64",
       "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/linux-amd64",
       "checksum": "415266d9af98578067051653f5057ea267c51ebf085408df48b118a8b978bac6",
-      "move-from": "linux-amd64",
+      "archive_path": "linux-amd64",
       "bin": "gobin"
     }
   ],
@@ -23,7 +23,8 @@
       "arch": "amd64",
       "url": "https://github.com/golangci/golangci-lint/releases/download/v1.21.0/golangci-lint-1.21.0-darwin-amd64.tar.gz",
       "checksum": "2b2713ec5007e67883aa501eebb81f22abfab0cf0909134ba90f60a066db3760",
-      "symlink": "golangci-lint-1.21.0-darwin-amd64/golangci-lint",
+      "archive_path": "golangci-lint-1.21.0-darwin-amd64/golangci-lint",
+      "link": true,
       "bin": "golangci-lint"
     },
     {
@@ -31,7 +32,8 @@
       "arch": "amd64",
       "url": "https://github.com/golangci/golangci-lint/releases/download/v1.21.0/golangci-lint-1.21.0-linux-amd64.tar.gz",
       "checksum": "2c861f8dc56b560474aa27cab0c075991628cc01af3451e27ac82f5d10d5106b",
-      "symlink": "golangci-lint-1.21.0-linux-amd64/golangci-lint",
+      "archive_path": "golangci-lint-1.21.0-linux-amd64/golangci-lint",
+      "link": true,
       "bin": "golangci-lint"
     }
   ],
@@ -41,7 +43,7 @@
       "arch": "amd64",
       "url": "https://github.com/protocolbuffers/protobuf/releases/download/v3.9.1/protoc-3.9.1-osx-x86_64.zip",
       "checksum": "fd2e8a52b9b173bf90633f346901f4dcf3083769ea24f917809932d29ffa1410",
-      "move-from": "bin/protoc",
+      "archive_path": "bin/protoc",
       "bin": "protoc"
     },
     {
@@ -49,7 +51,7 @@
       "arch": "amd64",
       "url": "https://github.com/protocolbuffers/protobuf/releases/download/v3.9.1/protoc-3.9.1-linux-x86_64.zip",
       "checksum": "77410d08e9a3c1ebb68afc13ee0c0fb4272c01c20bfd289adfb51b1c622bab07",
-      "move-from": "bin/protoc",
+      "archive_path": "bin/protoc",
       "bin": "protoc"
     }
   ],
@@ -59,7 +61,6 @@
       "arch": "amd64",
       "url": "https://github.com/goreleaser/goreleaser/releases/download/v0.120.7/goreleaser_Darwin_x86_64.tar.gz",
       "checksum": "2ec8bb354cca2936d0722e7da770c37e2ba6cc90de4a1cea186e20968c47b663",
-      "move-from": "goreleaser",
       "bin": "goreleaser"
     },
     {
@@ -67,7 +68,6 @@
       "arch": "amd64",
       "url": "https://github.com/goreleaser/goreleaser/releases/download/v0.120.7/goreleaser_Linux_x86_64.tar.gz",
       "checksum": "771f2ad8219078b16a3e82097e9805309f6516640f0c6ab6b87f9b085a8ad743",
-      "move-from": "goreleaser",
       "bin": "goreleaser"
     }
   ],
@@ -77,7 +77,6 @@
       "arch": "amd64",
       "url": "https://github.com/WillAbides/semver-next/releases/download/v0.4.0/semver-next_0.4.0_darwin_amd64.tar.gz",
       "checksum": "a519f2c3bbe8972deb094d56c196fec89496f663431321c22be343ced23839fb",
-      "move-from": "semver-next",
       "bin": "semver-next"
     },
     {
@@ -85,7 +84,6 @@
       "arch": "amd64",
       "url": "https://github.com/WillAbides/semver-next/releases/download/v0.4.0/semver-next_0.4.0_linux_amd64.tar.gz",
       "checksum": "6317c36bec63158038381e8878601151ae996310fef58306f70cb03f1b46ef7f",
-      "move-from": "semver-next",
       "bin": "semver-next"
     }
   ]