diff --git a/go.mod b/go.mod index 2a476b6..45fe464 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.15 require ( github.com/magefile/mage v1.11.0 + github.com/mholt/archiver/v3 v3.5.0 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.6.1 ) diff --git a/go.sum b/go.sum index 1f34a52..26a2d4e 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,26 @@ +github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= +github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= +github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A= +github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls= github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE= +github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc= +github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= +github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/pierrec/lz4/v4 v4.0.3 h1:vNQKSVZNYUEAvRY9FaUXAF1XPbSOHJtDTiP41kzDz2E= +github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -9,6 +28,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/pkg/archive/doc.go b/pkg/archive/doc.go new file mode 100644 index 0000000..8118b09 --- /dev/null +++ b/pkg/archive/doc.go @@ -0,0 +1,6 @@ +// Helper methods for working with archived/compressed files. +// +// These functions are separated into their own package to +// limit the dependencies pulled in when using magex if you +// are not using archived files. +package archive diff --git a/pkg/archive/install.go b/pkg/archive/install.go new file mode 100644 index 0000000..9a552a6 --- /dev/null +++ b/pkg/archive/install.go @@ -0,0 +1,75 @@ +package archive + +import ( + "log" + "os" + "path/filepath" + "runtime" + + "github.com/carolynvs/magex/pkg/downloads" + "github.com/carolynvs/magex/xplat" + "github.com/mholt/archiver/v3" + _ "github.com/mholt/archiver/v3" + "github.com/pkg/errors" +) + +// DownloadArchiveOptions are the set of options available for DownloadToGopathBin. +type DownloadArchiveOptions struct { + downloads.DownloadOptions + + // ArchiveExtensions maps from the GOOS to the expected extension. Required. + // For example, windows may use .zip while darwin/linux uses .tgz. + ArchiveExtensions map[string]string + + // TargetFileTemplate specifies the path to the target binary in the archive. Required. + // Supports the same templating as downloads.DownloadOptions.UrlTemplate. + TargetFileTemplate string +} + +// DownloadToGopathBin downloads an archived file to GOPATH/bin. +func DownloadToGopathBin(opts DownloadArchiveOptions) error { + // determine the appropriate file extension based on the OS, e.g. windows gets .zip, otherwise .tgz + opts.Ext = opts.ArchiveExtensions[runtime.GOOS] + if opts.Ext == "" { + return errors.Errorf("no archive file extension was specified for the current GOOS (%s)", runtime.GOOS) + } + + if opts.Hook == nil { + opts.Hook = ExtractBinaryFromArchiveHook(opts) + } + + return downloads.DownloadToGopathBin(opts.DownloadOptions) +} + +// ExtractBinaryFromArchiveHook is the default hook for DownloadToGopathBin. +func ExtractBinaryFromArchiveHook(opts DownloadArchiveOptions) downloads.PostDownloadHook { + return func(archiveFile string) (binPath string, err error) { + // Save the binary next to the archive file in the temp directory + outDir := filepath.Dir(archiveFile) + + // Render the name of the file in the archive + opts.Ext = xplat.FileExt() + targetFile, err := downloads.RenderTemplate(opts.TargetFileTemplate, opts.DownloadOptions) + if err != nil { + return "", errors.Wrapf(err, "error rendering TargetFileTemplate") + } + + log.Printf("extracting %s from %s...\n", targetFile, archiveFile) + + // Extract the binary + err = archiver.Extract(archiveFile, targetFile, outDir) + if err != nil { + return "", errors.Wrapf(err, "unable to unpack %s", archiveFile) + } + + // The extracted file may be nested depending on its position in the archive + binFile := filepath.Join(outDir, targetFile) + + // Check that file was extracted, Extract doesn't error out if you give it a missing targetFile + if _, err := os.Stat(binFile); os.IsNotExist(err) { + return "", errors.Errorf("could not find %s in the archive", targetFile) + } + + return binFile, nil + } +} diff --git a/pkg/archive/install_example_test.go b/pkg/archive/install_example_test.go new file mode 100644 index 0000000..d97ab48 --- /dev/null +++ b/pkg/archive/install_example_test.go @@ -0,0 +1,33 @@ +package archive_test + +import ( + "log" + + "github.com/carolynvs/magex/pkg/archive" + "github.com/carolynvs/magex/pkg/downloads" + "github.com/carolynvs/magex/pkg/gopath" +) + +func ExampleDownloadToGopathBin() { + opts := archive.DownloadArchiveOptions{ + DownloadOptions: downloads.DownloadOptions{ + UrlTemplate: "https://get.helm.sh/helm-{{.VERSION}}-{{.GOOS}}-{{.GOARCH}}{{.EXT}}", + Name: "helm", + Version: "v3.5.3", + }, + ArchiveExtensions: map[string]string{ + "darwin": ".tar.gz", + "linux": ".tar.gz", + "windows": ".zip", + }, + TargetFileTemplate: "{{.GOOS}}-{{.GOARCH}}/helm{{.EXT}}", + } + err := archive.DownloadToGopathBin(opts) + if err != nil { + log.Fatal("could not download helm") + } + + // Add GOPATH/bin to PATH if necessary so that we can immediately + // use the installed tool + gopath.EnsureGopathBin() +} diff --git a/pkg/archive/install_test.go b/pkg/archive/install_test.go new file mode 100644 index 0000000..2b29de0 --- /dev/null +++ b/pkg/archive/install_test.go @@ -0,0 +1,50 @@ +package archive + +import ( + "os" + "os/exec" + "runtime" + "testing" + + "github.com/carolynvs/magex/pkg/downloads" + "github.com/carolynvs/magex/pkg/gopath" + "github.com/carolynvs/magex/xplat" + "github.com/magefile/mage/mg" + "github.com/stretchr/testify/require" +) + +func TestDownloadArchiveToGopathBin(t *testing.T) { + os.Setenv(mg.VerboseEnv, "true") + err, cleanup := gopath.UseTempGopath() + require.NoError(t, err, "Failed to set up a temporary GOPATH") + defer cleanup() + + // gh cli unfortunately uses a different archive schema depending on the OS + tmpl := "gh_{{.VERSION}}_{{.GOOS}}_{{.GOARCH}}/bin/gh{{.EXT}}" + if runtime.GOOS == "windows" { + tmpl = "bin/gh.exe" + } + + opts := DownloadArchiveOptions{ + DownloadOptions: downloads.DownloadOptions{ + UrlTemplate: "https://github.com/cli/cli/releases/download/v{{.VERSION}}/gh_{{.VERSION}}_{{.GOOS}}_{{.GOARCH}}{{.EXT}}", + Name: "gh", + Version: "1.8.1", + OsReplacement: map[string]string{ + "darwin": "macOS", + }, + }, + ArchiveExtensions: map[string]string{ + "linux": ".tar.gz", + "darwin": ".tar.gz", + "windows": ".zip", + }, + TargetFileTemplate: tmpl, + } + + err = DownloadToGopathBin(opts) + require.NoError(t, err) + + _, err = exec.LookPath("gh" + xplat.FileExt()) + require.NoError(t, err) +} diff --git a/pkg/build.go b/pkg/build.go deleted file mode 100644 index de674b7..0000000 --- a/pkg/build.go +++ /dev/null @@ -1,15 +0,0 @@ -package pkg - -import ( - "go/build" - "os" -) - -// GOPATH returns the current GOPATH. -func GOPATH() string { - gopath := os.Getenv("GOPATH") - if gopath != "" { - return gopath - } - return build.Default.GOPATH -} diff --git a/pkg/downloads/download.go b/pkg/downloads/download.go new file mode 100644 index 0000000..ddfb0d9 --- /dev/null +++ b/pkg/downloads/download.go @@ -0,0 +1,161 @@ +package downloads + +import ( + "bytes" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path/filepath" + "runtime" + "text/template" + + "github.com/carolynvs/magex/pkg/gopath" + "github.com/carolynvs/magex/shx" + "github.com/carolynvs/magex/xplat" + "github.com/pkg/errors" +) + +// PostDownloadHook is the handler called after downloading a file, which returns the absolute path to the binary. +type PostDownloadHook func(archivePath string) (string, error) + +// DownloadOptions +type DownloadOptions struct { + // UrlTemplate is the Go template for the URL to download. Required. + // Available Template Variables: + // - {{.GOOS}} + // - {{.GOARCH}} + // - {{.EXT}} + // - {{.VERSION}} + UrlTemplate string + + // Name of the binary, excluding OS specific file extension. Required. + Name string + + // Version to replace {{.VERSION}} in the URL template. Optional depending on whether or not the version is in the UrlTemplate. + Version string + + // Ext to replace {{.EXT}} in the URL template. Optional, defaults to xplat.FileExt(). + Ext string + + // OsReplacement maps from a GOOS to the os keyword used for the download. Optional, defaults to empty. + OsReplacement map[string]string + + // ArchReplacement maps from a GOARCH to the arch keyword used for the download. Optional, defaults to empty. + ArchReplacement map[string]string + + // Hook to call after downloading the file. + Hook PostDownloadHook +} + +// DownloadToGopathBin takes a Go templated URL and expands template variables +// - srcTemplate is the URL +// - version is the version to substitute into the template +// - ext is the file extension to substitute into the template +// +// Template Variables: +// - {{.GOOS}} +// - {{.GOARCH}} +// - {{.EXT}} +// - {{.VERSION}} +func DownloadToGopathBin(opts DownloadOptions) error { + src, err := RenderTemplate(opts.UrlTemplate, opts) + if err != nil { + return err + } + log.Printf("Downloading %s...", src) + + err = gopath.EnsureGopathBin() + if err != nil { + return err + } + + // Download to a temp file + tmpDir, err := ioutil.TempDir("", "magex") + if err != nil { + return errors.Wrap(err, "could not create temporary directory") + } + defer os.RemoveAll(tmpDir) + tmpFile := filepath.Join(tmpDir, filepath.Base(src)) + + r, err := http.Get(src) + if err != nil { + return errors.Wrapf(err, "could not resolve %s", src) + } + defer r.Body.Close() + + f, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0755) + if err != nil { + return errors.Wrapf(err, "could not open %s", tmpFile) + } + defer f.Close() + + // Download to the temp file + _, err = io.Copy(f, r.Body) + if err != nil { + errors.Wrapf(err, "error downloading %s", src) + } + f.Close() + + // Call a hook to allow for extracting or modifying the downloaded file + var tmpBin = tmpFile + if opts.Hook != nil { + tmpBin, err = opts.Hook(tmpFile) + if err != nil { + return err + } + } + + // Make the binary executable + err = os.Chmod(tmpBin, 0755) + if err != nil { + return errors.Wrapf(err, "could not make %s executable", tmpBin) + } + + // Move it to GOPATH/bin + dest := filepath.Join(gopath.GetGopathBin(), opts.Name+xplat.FileExt()) + err = shx.Copy(tmpBin, dest) + return errors.Wrapf(err, "error copying %s to %s", tmpBin, dest) +} + +// RenderTemplate takes a Go templated string and expands template variables +// Available Template Variables: +// - {{.GOOS}} +// - {{.GOARCH}} +// - {{.EXT}} +// - {{.VERSION}} +func RenderTemplate(tmplContents string, opts DownloadOptions) (string, error) { + tmpl, err := template.New("url").Parse(tmplContents) + if err != nil { + return "", errors.Wrapf(err, "error parsing %s as a Go template", opts.UrlTemplate) + } + + srcData := struct { + GOOS string + GOARCH string + EXT string + VERSION string + }{ + GOOS: runtime.GOOS, + GOARCH: runtime.GOARCH, + EXT: opts.Ext, + VERSION: opts.Version, + } + + if overrideGoos, ok := opts.OsReplacement[runtime.GOOS]; ok { + srcData.GOOS = overrideGoos + } + + if overrideGoarch, ok := opts.ArchReplacement[runtime.GOARCH]; ok { + srcData.GOARCH = overrideGoarch + } + + buf := &bytes.Buffer{} + err = tmpl.Execute(buf, srcData) + if err != nil { + return "", errors.Wrapf(err, "error rendering %s as a Go template with data: %#v", opts.UrlTemplate, srcData) + } + + return buf.String(), nil +} diff --git a/pkg/gopath/gopath.go b/pkg/gopath/gopath.go new file mode 100644 index 0000000..64b3d3d --- /dev/null +++ b/pkg/gopath/gopath.go @@ -0,0 +1,69 @@ +package gopath + +import ( + "go/build" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/carolynvs/magex/xplat" + "github.com/pkg/errors" +) + +// EnsureGopathBin ensures that GOPATH/bin exists and is in PATH. +// Detects if this is an Azure CI build and exports the updated PATH. +func EnsureGopathBin() error { + gopathBin := GetGopathBin() + err := os.MkdirAll(gopathBin, 0755) + if err != nil { + errors.Wrapf(err, "could not create GOPATH/bin at %s", gopathBin) + } + xplat.EnsureInPath(GetGopathBin()) + return nil +} + +// GetGopathBin returns GOPATH/bin. +func GetGopathBin() string { + return filepath.Join(GOPATH(), "bin") +} + +// GOPATH returns the current GOPATH. +func GOPATH() string { + gopath := os.Getenv("GOPATH") + if gopath != "" { + return gopath + } + return build.Default.GOPATH +} + +// UseTempGopath sets the current GOPATH environment variable to a temporary +// directory, returning a cleanup function which reverts the change. +func UseTempGopath() (error, func()) { + oldpath := os.Getenv("PATH") + tmp, err := ioutil.TempDir("", "magex") + if err != nil { + return errors.Wrap(err, "Failed to create a temp directory"), func() {} + } + + cleanup := func() { + os.RemoveAll(tmp) + defer os.Setenv("PATH", oldpath) + defer os.Setenv("GOPATH", build.Default.GOPATH) + } + + // Remove actual GOPATH/bin from PATH so the test doesn't accidentally pass because the package was installed before the test was run + gopathBin := filepath.Join(build.Default.GOPATH, "bin") + os.Setenv("PATH", strings.ReplaceAll(oldpath, gopathBin, "")) + + // Use temp dir for GOPATH + os.Setenv("GOPATH", tmp) + + err = EnsureGopathBin() + if err != nil { + cleanup() + return err, nil + } + + return nil, cleanup +} diff --git a/pkg/install.go b/pkg/install.go index cc7b2c6..e8b6b76 100644 --- a/pkg/install.go +++ b/pkg/install.go @@ -1,21 +1,18 @@ package pkg import ( - "bytes" "fmt" - "html/template" - "io" "io/ioutil" "log" - "net/http" "os" "os/exec" "path" "path/filepath" "regexp" - "runtime" "strings" + "github.com/carolynvs/magex/pkg/downloads" + "github.com/carolynvs/magex/pkg/gopath" "github.com/carolynvs/magex/shx" "github.com/carolynvs/magex/xplat" "github.com/pkg/errors" @@ -70,7 +67,7 @@ func getCommandName(pkg string) string { // When version is specified, install that version. Otherwise install the most // recent code from the default branch. func InstallPackage(pkg string, version string) error { - EnsureGopathBin() + gopath.EnsureGopathBin() cmd := getCommandName(pkg) @@ -148,23 +145,6 @@ func IsCommandAvailable(cmd string, version string, versionArgs ...string) (bool return versionFound, nil } -// GetGopathBin returns GOPATH/bin. -func GetGopathBin() string { - return filepath.Join(GOPATH(), "bin") -} - -// EnsureGopathBin ensures that GOPATH/bin exists and is in PATH. -// Detects if this is an Azure CI build and exports the updated PATH. -func EnsureGopathBin() error { - gopathBin := GetGopathBin() - err := os.MkdirAll(gopathBin, 0755) - if err != nil { - errors.Wrapf(err, "could not create GOPATH/bin at %s", gopathBin) - } - xplat.EnsureInPath(GetGopathBin()) - return nil -} - // DownloadToGopathBin downloads an executable file to GOPATH/bin. // src can include the following template values: // - {{.GOOS}} @@ -172,71 +152,11 @@ func EnsureGopathBin() error { // - {{.EXT}} // - {{.VERSION}} func DownloadToGopathBin(srcTemplate string, name string, version string) error { - src, err := renderUrlTemplate(srcTemplate, version) - if err != nil { - return err - } - log.Printf("Downloading %s to $GOPATH/bin\n", src) - - err = EnsureGopathBin() - if err != nil { - return err - } - - // Download to a temp file - f, err := ioutil.TempFile("", path.Base(src)) - if err != nil { - return errors.Wrap(err, "could not create temp file") - } - defer f.Close() - - // Make it executable - err = os.Chmod(f.Name(), 0755) - if err != nil { - return errors.Wrapf(err, "could not make %s executable", f.Name()) - } - - r, err := http.Get(src) - if err != nil { - return errors.Wrapf(err, "could not resolve %s", src) - } - defer r.Body.Close() - - _, err = io.Copy(f, r.Body) - if err != nil { - errors.Wrapf(err, "error downloading %s", src) - } - f.Close() - - // Move it to GOPATH/bin - dest := filepath.Join(GetGopathBin(), name+xplat.FileExt()) - err = os.Rename(f.Name(), dest) - return errors.Wrapf(err, "error moving %s to %s", src, dest) -} - -func renderUrlTemplate(srcTemplate string, version string) (string, error) { - tmpl, err := template.New("url").Parse(srcTemplate) - if err != nil { - return "", errors.Wrapf(err, "error parsing %s as a Go template", srcTemplate) - } - - srcData := struct { - GOOS string - GOARCH string - EXT string - VERSION string - }{ - GOOS: runtime.GOOS, - GOARCH: runtime.GOARCH, - EXT: xplat.FileExt(), - VERSION: version, + opts := downloads.DownloadOptions{ + UrlTemplate: srcTemplate, + Name: name, + Version: version, + Ext: xplat.FileExt(), } - - buf := &bytes.Buffer{} - err = tmpl.Execute(buf, srcData) - if err != nil { - return "", errors.Wrapf(err, "error rendering %s as a Go template with data: %#v", srcTemplate, srcData) - } - - return buf.String(), nil + return downloads.DownloadToGopathBin(opts) } diff --git a/pkg/install_example_test.go b/pkg/install_example_test.go index b8e8e1b..d096e2d 100644 --- a/pkg/install_example_test.go +++ b/pkg/install_example_test.go @@ -4,6 +4,7 @@ import ( "log" "github.com/carolynvs/magex/pkg" + "github.com/carolynvs/magex/pkg/gopath" ) func ExampleEnsureMage() { @@ -41,5 +42,5 @@ func ExampleDownloadToGopathBin() { // Add GOPATH/bin to PATH if necessary so that we can immediately // use the installed tool - pkg.EnsureGopathBin() + gopath.EnsureGopathBin() } diff --git a/pkg/install_test.go b/pkg/install_test.go index 2ddb775..ca0393a 100644 --- a/pkg/install_test.go +++ b/pkg/install_test.go @@ -1,30 +1,28 @@ package pkg import ( - "go/build" - "io/ioutil" "os" - "path/filepath" - "strings" + "os/exec" "testing" + "github.com/carolynvs/magex/pkg/gopath" "github.com/carolynvs/magex/xplat" "github.com/magefile/mage/mg" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDownloadToGopathBin(t *testing.T) { + err, cleanup := gopath.UseTempGopath() + require.NoError(t, err, "Failed to set up a temporary GOPATH") + defer cleanup() + url := "https://storage.googleapis.com/kubernetes-release/release/{{.VERSION}}/bin/{{.GOOS}}/{{.GOARCH}}/kubectl{{.EXT}}" - err := DownloadToGopathBin(url, "kubectl", "v1.19.0") + err = DownloadToGopathBin(url, "kubectl", "v1.19.0") require.NoError(t, err) - dest := filepath.Join(GOPATH(), "bin/kubectl"+xplat.FileExt()) - _, err = os.Stat(dest) + _, err = exec.LookPath("kubectl" + xplat.FileExt()) require.NoError(t, err) - - os.Remove(dest) } func TestGetCommandName(t *testing.T) { @@ -46,35 +44,12 @@ func TestGetCommandName(t *testing.T) { func TestEnsurePackage_MajorVersion(t *testing.T) { os.Setenv(mg.VerboseEnv, "true") - err, cleanup := UseTempGopath(t) - defer cleanup() + err, cleanup := gopath.UseTempGopath() require.NoError(t, err, "Failed to set up a temporary GOPATH") + defer cleanup() hasCmd, err := IsCommandAvailable("testpkg", "") require.False(t, hasCmd) err = EnsurePackage("github.com/carolynvs/testpkg/v2", "v2.0.1", "--version") require.NoError(t, err) } - -func UseTempGopath(t *testing.T) (error, func()) { - oldpath := os.Getenv("PATH") - tmp, err := ioutil.TempDir("", "magex") - if err != nil { - return errors.Wrap(err, "Failed to create a temp directory"), func() {} - } - - cleanup := func() { - os.RemoveAll(tmp) - defer os.Setenv("PATH", oldpath) - defer os.Setenv("GOPATH", build.Default.GOPATH) - } - - // Remove actual GOPATH/bin from PATH so the test doesn't accidentally pass because the package was installed before the test was run - gopathBin := filepath.Join(build.Default.GOPATH, "bin") - os.Setenv("PATH", strings.ReplaceAll(oldpath, gopathBin, "")) - - // Use temp dir for GOPATH - os.Setenv("GOPATH", tmp) - - return EnsureGopathBin(), cleanup -}