From b71d6dc84749f49b3202fff52c683fb8b3f22a7e Mon Sep 17 00:00:00 2001 From: WillAbides Date: Tue, 26 Nov 2019 18:15:31 -0600 Subject: [PATCH] refactor for testability (#36) --- Makefile | 6 + bindown.go | 86 ------- bindown_test.go | 95 -------- cmd/bindown/config.go | 119 ++-------- cmd/bindown/download.go | 15 +- downloader.go | 186 ++++++++------- downloader_test.go | 215 +++++++++++++----- go.mod | 5 + go.sum | 43 ++++ .../testhelper/testhelper.go | 35 +-- util.go => internal/util/util.go | 68 +++--- pkg/config/config.go | 169 ++++++++++++++ pkg/config/config_test.go | 177 ++++++++++++++ pkg/config/internal/mocks/mock_config.go | 131 +++++++++++ script/fmt | 4 +- script/generate | 10 + testdata/config/ex1.json | 21 ++ testdata/config/oldformat.json | 19 ++ testdata/config/unknownfield.json | 22 ++ 19 files changed, 948 insertions(+), 478 deletions(-) delete mode 100644 bindown.go delete mode 100644 bindown_test.go rename testhelper_test.go => internal/testhelper/testhelper.go (53%) rename util.go => internal/util/util.go (53%) create mode 100644 pkg/config/config.go create mode 100644 pkg/config/config_test.go create mode 100644 pkg/config/internal/mocks/mock_config.go create mode 100755 script/generate create mode 100644 testdata/config/ex1.json create mode 100644 testdata/config/oldformat.json create mode 100644 testdata/config/unknownfield.json diff --git a/Makefile b/Makefile index 262f8f4..9df1603 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,12 @@ bin/semver-next: bin/bindown bin/bindown download $@ bins += bin/semver-next +MOCKGEN_REF := 9fa652df1129bef0e734c9cf9bf6dbae9ef3b9fa +bin/mockgen: bin/gobin + GOBIN=${CURDIR}/bin \ + bin/gobin github.com/golang/mock/mockgen@$(MOCKGEN_REF); +bins += bin/mockgen + GOIMPORTS_REF := 8aaa1484dc108aa23dcf2d4a09371c0c9e280f6b bin/goimports: bin/gobin GOBIN=${CURDIR}/bin \ diff --git a/bindown.go b/bindown.go deleted file mode 100644 index c650ea2..0000000 --- a/bindown.go +++ /dev/null @@ -1,86 +0,0 @@ -package bindown - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "strings" -) - -//LoadConfig returns a Config from a config reader -func LoadConfig(config io.Reader) (*Config, error) { - configBytes, err := ioutil.ReadAll(config) - if err != nil { - return nil, err - } - decoder := json.NewDecoder(bytes.NewReader(configBytes)) - decoder.DisallowUnknownFields() - var cfg Config - err = decoder.Decode(&cfg) - if err != nil { - decoder = json.NewDecoder(bytes.NewReader(configBytes)) - decoder.DisallowUnknownFields() - dls := cfg.Downloaders - err = decoder.Decode(&dls) - if err == nil { - cfg.Downloaders = dls - } - } - if err != nil { - return nil, err - } - return &cfg, err -} - -//LoadConfigFile returns a Config from the path to a config file -func LoadConfigFile(configFile string) (*Config, error) { - configReader, err := os.Open(configFile) //nolint:gosec - if err != nil { - return nil, fmt.Errorf("couldn't read config file: %s", configFile) - } - defer logCloseErr(configReader) - return LoadConfig(configReader) -} - -//Config is downloaders configuration -type Config struct { - Downloaders map[string][]*Downloader `json:"downloaders,omitempty"` -} - -// Downloader returns a Downloader for the given binary, os and arch. -func (c *Config) Downloader(binary, os, arch string) *Downloader { - l, ok := c.Downloaders[binary] - if !ok { - return nil - } - for _, d := range l { - if !eqOS(os, d.OS) { - continue - } - if strings.EqualFold(arch, d.Arch) { - return d - } - } - return nil -} - -func eqOS(a, b string) bool { - return strings.EqualFold(normalizeOS(a), normalizeOS(b)) -} - -var osAliases = map[string]string{ - "osx": "darwin", - "macos": "darwin", -} - -func normalizeOS(os string) string { - for alias, aliasedOs := range osAliases { - if strings.EqualFold(alias, os) { - return aliasedOs - } - } - return strings.ToLower(os) -} diff --git a/bindown_test.go b/bindown_test.go deleted file mode 100644 index d19a855..0000000 --- a/bindown_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package bindown - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestLoadConfig(t *testing.T) { - t.Run("current format", func(t *testing.T) { - dir, teardown := tmpDir(t) - defer teardown() - file := filepath.Join(dir, "bindown.json") - - // language=json - content := ` -{ - "downloaders": { - "gobin": [ - { - "os": "darwin", - "arch": "amd64", - "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/darwin-amd64", - "checksum": "84ed966949e06bebd7d006bc343caf9d736932fd8b37df5cb5b268a28d07bd30", - "archive_path": "darwin-amd64", - "link": true - }, - { - "os": "linux", - "arch": "amd64", - "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/linux-amd64", - "checksum": "415266d9af98578067051653f5057ea267c51ebf085408df48b118a8b978bac6", - "archive_path": "linux-amd64" - } - ] - } -} -` - err := ioutil.WriteFile(file, []byte(content), 0640) - require.NoError(t, err) - fileReader, err := os.Open(file) - require.NoError(t, err) - defer func() { - require.NoError(t, fileReader.Close()) - }() - cfg, err := LoadConfig(fileReader) - assert.NoError(t, err) - assert.Equal(t, "darwin-amd64", cfg.Downloaders["gobin"][0].ArchivePath) - assert.True(t, cfg.Downloaders["gobin"][0].Link) - }) - - t.Run("downloaders only", func(t *testing.T) { - dir, teardown := tmpDir(t) - defer teardown() - file := filepath.Join(dir, "bindown.json") - - // language=json - content := ` -{ - "gobin": [ - { - "os": "darwin", - "arch": "amd64", - "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/darwin-amd64", - "checksum": "84ed966949e06bebd7d006bc343caf9d736932fd8b37df5cb5b268a28d07bd30", - "archive_path": "darwin-amd64", - "link": true - }, - { - "os": "linux", - "arch": "amd64", - "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/linux-amd64", - "checksum": "415266d9af98578067051653f5057ea267c51ebf085408df48b118a8b978bac6", - "archive_path": "linux-amd64" - } - ] -} -` - err := ioutil.WriteFile(file, []byte(content), 0640) - require.NoError(t, err) - fileReader, err := os.Open(file) - require.NoError(t, err) - defer func() { - require.NoError(t, fileReader.Close()) - }() - cfg, err := LoadConfig(fileReader) - require.NoError(t, err) - assert.Equal(t, "darwin-amd64", cfg.Downloaders["gobin"][0].ArchivePath) - assert.True(t, cfg.Downloaders["gobin"][0].Link) - }) -} diff --git a/cmd/bindown/config.go b/cmd/bindown/config.go index 72022bb..929307b 100644 --- a/cmd/bindown/config.go +++ b/cmd/bindown/config.go @@ -1,15 +1,12 @@ package main import ( - "encoding/json" "fmt" - "io/ioutil" - "os" - "path" "path/filepath" "github.com/alecthomas/kong" - "github.com/willabides/bindown/v2" + "github.com/willabides/bindown/v2/pkg/config" + "go.uber.org/multierr" ) var configKongVars = kong.Vars{ @@ -29,67 +26,28 @@ type configCmd struct { type configFmtCmd struct{} func (c configFmtCmd) Run() error { - config, err := bindown.LoadConfigFile(cli.Configfile) + configFile, err := config.NewConfigFile(cli.Configfile) if err != nil { return err } - b, err := json.MarshalIndent(&config, "", " ") - if err != nil { - return err - } - return ioutil.WriteFile(cli.Configfile, b, 0600) + return configFile.WriteFile() } type configUpdateChecksumsCmd struct { TargetFile string `kong:"required=true,arg,help=${config_checksums_bin_help}"` } -func (d *configUpdateChecksumsCmd) Run(kctx *kong.Context) error { - config, err := bindown.LoadConfigFile(cli.Configfile) - if err != nil { - return fmt.Errorf("error loading config from %q", cli.Configfile) - } - tmpDir, err := ioutil.TempDir("", "bindown") +func (d *configUpdateChecksumsCmd) Run(*kong.Context) error { + configFile, err := config.NewConfigFile(cli.Configfile) if err != nil { return err } - defer func() { - err = os.RemoveAll(tmpDir) - if err != nil { - kctx.Errorf("error deleting temp directory, %q", tmpDir) - } - }() - - binary := path.Base(d.TargetFile) - binDir := path.Dir(d.TargetFile) - - cellarDir := cli.CellarDir - if cellarDir == "" { - cellarDir = filepath.Join(tmpDir, "cellar") - } - - downloaders, ok := config.Downloaders[binary] - if !ok { - return fmt.Errorf("nothing configured for %q", binary) - } - - for _, downloader := range downloaders { - err = downloader.UpdateChecksum(bindown.UpdateChecksumOpts{ - DownloaderName: binary, - CellarDir: cellarDir, - TargetDir: binDir, - }) - if err != nil { - return err - } - } - - b, err := json.MarshalIndent(&config, "", " ") + binary := filepath.Base(d.TargetFile) + err = configFile.UpdateChecksums(binary, cli.CellarDir) if err != nil { return err } - - return ioutil.WriteFile(cli.Configfile, b, 0600) + return configFile.WriteFile() } type configValidateCmd struct { @@ -97,60 +55,17 @@ type configValidateCmd struct { } func (d configValidateCmd) Run(kctx *kong.Context) error { - config, err := bindown.LoadConfigFile(cli.Configfile) - if err != nil { - return fmt.Errorf("error loading config from %q", cli.Configfile) - } - tmpDir, err := ioutil.TempDir("", "bindown") + config, err := config.NewConfigFile(cli.Configfile) if err != nil { return err } - defer func() { - err = os.RemoveAll(tmpDir) - if err != nil { - kctx.Errorf("error deleting temp directory, %q", tmpDir) - } - }() - - binary := path.Base(d.Bin) - binDir := filepath.Join(tmpDir, "bin") - err = os.MkdirAll(binDir, 0700) - if err != nil { - return err - } - - cellarDir := cli.CellarDir - if cellarDir == "" { - cellarDir = filepath.Join(tmpDir, "cellar") + err = config.Validate(d.Bin, cli.CellarDir) + if err == nil { + return nil } - - downloaders, ok := config.Downloaders[binary] - if !ok { - return fmt.Errorf("nothing configured for %q", binary) - } - - for _, downloader := range downloaders { - dlJSON, err := json.MarshalIndent(downloader, "", " ") - if err != nil { - return err - } - - installOpts := bindown.InstallOpts{ - DownloaderName: binary, - TargetDir: binDir, - Force: true, - CellarDir: cellarDir, - } - - err = downloader.Install(installOpts) - if err != nil { - return fmt.Errorf("could not validate downloader:\n%s", string(dlJSON)) - } - - err = os.Remove(filepath.Join(binDir, downloader.BinName)) - if err != nil { - return err - } + errs := multierr.Errors(err) + for _, gotErr := range errs { + kctx.Printf("%s\n", gotErr.Error()) } - return nil + return fmt.Errorf("could not validate") } diff --git a/cmd/bindown/download.go b/cmd/bindown/download.go index 2b3a39d..ec251d5 100644 --- a/cmd/bindown/download.go +++ b/cmd/bindown/download.go @@ -6,7 +6,7 @@ import ( "runtime" "github.com/alecthomas/kong" - "github.com/willabides/bindown/v2" + "github.com/willabides/bindown/v2/pkg/config" ) var downloadKongVars = kong.Vars{ @@ -26,14 +26,14 @@ type downloadCmd struct { } func (d *downloadCmd) Run(*kong.Context) error { - config, err := bindown.LoadConfigFile(cli.Configfile) + cfg, err := config.NewConfigFile(cli.Configfile) if err != nil { return fmt.Errorf("error loading config from %q", cli.Configfile) } binary := path.Base(d.TargetFile) binDir := path.Dir(d.TargetFile) - downloader := config.Downloader(binary, d.OS, d.Arch) + downloader := cfg.Downloader(binary, d.OS, d.Arch) if downloader == nil { return fmt.Errorf(`no downloader configured for: bin: %s @@ -41,12 +41,5 @@ os: %s arch: %s`, binary, d.OS, d.Arch) } - installOpts := bindown.InstallOpts{ - DownloaderName: binary, - TargetDir: binDir, - Force: d.Force, - CellarDir: cli.CellarDir, - } - - return downloader.Install(installOpts) + return downloader.Install(binary, cli.CellarDir, binDir, d.Force) } diff --git a/downloader.go b/downloader.go index f145cfa..3e4f699 100644 --- a/downloader.go +++ b/downloader.go @@ -10,8 +10,10 @@ import ( "os" "path" "path/filepath" + "strings" "github.com/mholt/archiver/v3" + "github.com/willabides/bindown/v2/internal/util" ) // Downloader downloads a binary @@ -31,6 +33,32 @@ type Downloader struct { LinkSource string `json:"symlink,omitempty"` } +//ErrString string that represents the downloader in error messages +func (d *Downloader) ErrString(binary string) string { + if binary == "" { + binary = d.BinName + } + if d == nil { + return "" + } + return fmt.Sprintf("%s - %s - %s", binary, d.OS, d.Arch) +} + +//MatchesOS has an OS value matching opSys +func (d *Downloader) MatchesOS(opSys string) bool { + return eqOS(opSys, d.OS) +} + +//MatchesArch has an Arch value matching arch +func (d *Downloader) MatchesArch(arch string) bool { + return strings.EqualFold(arch, d.Arch) +} + +//HasChecksum has given checksum +func (d *Downloader) HasChecksum(checksum string) bool { + return checksum == d.Checksum +} + func (d *Downloader) downloadableName() (string, error) { u, err := url.Parse(d.URL) if err != nil { @@ -47,15 +75,7 @@ func (d *Downloader) downloadablePath(targetDir string) (string, error) { return filepath.Join(targetDir, name), nil } -func (d *Downloader) binPath(targetDir string) string { - return filepath.Join(targetDir, d.BinName) -} - -func (d *Downloader) chmod(targetDir string) error { - return os.Chmod(d.binPath(targetDir), 0755) //nolint:gosec -} - -func (d *Downloader) moveOrLinkBin(targetDir, extractDir string) error { +func (d *Downloader) moveOrLinkBin(target, extractDir string) error { //noinspection GoDeprecation if d.LinkSource != "" { d.ArchivePath = d.LinkSource @@ -67,12 +87,11 @@ func (d *Downloader) moveOrLinkBin(targetDir, extractDir string) error { } archivePath := filepath.FromSlash(d.ArchivePath) if archivePath == "" { - archivePath = filepath.FromSlash(d.BinName) + archivePath = filepath.Base(target) } var err error - target := d.binPath(targetDir) - if fileExists(target) { - err = rm(target) + if util.FileExists(target) { + err = util.Rm(target) if err != nil { return err } @@ -85,6 +104,7 @@ func (d *Downloader) moveOrLinkBin(targetDir, extractDir string) error { extractedBin := filepath.Join(extractDir, archivePath) if d.Link { + var targetDir string targetDir, err = filepath.Abs(filepath.Dir(target)) if err != nil { return err @@ -125,7 +145,7 @@ func (d *Downloader) extract(downloadDir, extractDir string) error { tarPath := filepath.Join(downloadDir, dlName) _, err = archiver.ByExtension(dlName) if err != nil { - return copyFile(tarPath, filepath.Join(extractDir, dlName)) + return util.CopyFile(tarPath, filepath.Join(extractDir, dlName)) } return archiver.Unarchive(tarPath, extractDir) } @@ -139,7 +159,7 @@ func (d *Downloader) download(downloadDir string) error { if err != nil { return err } - ok, err := fileExistsWithChecksum(dlPath, d.Checksum) + ok, err := util.FileExistsWithChecksum(dlPath, d.Checksum) if err != nil { return err } @@ -149,57 +169,48 @@ func (d *Downloader) download(downloadDir string) error { return downloadFile(dlPath, d.URL) } -func (d *Downloader) setDefaultBinName(defaultName string) { - if d.BinName == "" { - d.BinName = defaultName - } -} - func (d *Downloader) validateChecksum(targetDir string) error { targetFile, err := d.downloadablePath(targetDir) if err != nil { return err } - result, err := fileChecksum(targetFile) + result, err := util.FileChecksum(targetFile) + if err != nil { + return err + } + dlName, err := d.downloadableName() if err != nil { return err } if d.Checksum != result { defer func() { - delErr := rm(targetFile) + delErr := util.Rm(targetFile) if delErr != nil { log.Printf("Error deleting suspicious file at %q. Please delete it manually", targetFile) } }() return fmt.Errorf(`checksum mismatch in downloaded file %q wanted: %s -got: %s`, targetFile, d.Checksum, result) +got: %s`, dlName, d.Checksum, result) } return nil } -//UpdateChecksumOpts options for UpdateChecksum -type UpdateChecksumOpts struct { - // DownloaderName is the downloader's key from the config file - DownloaderName string - // CellarDir is the directory where downloads and extractions will be placed. Default is a /.bindown - CellarDir string - // TargetDir is the directory where the executable should end up - TargetDir string -} - //UpdateChecksum updates the checksum based on a fresh download -func (d *Downloader) UpdateChecksum(opts UpdateChecksumOpts) error { - cellarDir := opts.CellarDir +func (d *Downloader) UpdateChecksum(cellarDir string) error { if cellarDir == "" { - cellarDir = filepath.Join(opts.TargetDir, ".bindown") + tmpDir, tmpTeardown, err := util.TmpDir() + if err != nil { + return err + } + defer tmpTeardown() + cellarDir = filepath.Join(tmpDir, "cellar") } downloadDir := filepath.Join(cellarDir, "downloads", d.downloadsSubName()) err := d.download(downloadDir) if err != nil { - log.Printf("error downloading: %v", err) return err } @@ -208,7 +219,7 @@ func (d *Downloader) UpdateChecksum(opts UpdateChecksumOpts) error { return err } - checkSum, err := fileChecksum(dlPath) + checkSum, err := util.FileChecksum(dlPath) if err != nil { return err } @@ -217,38 +228,25 @@ func (d *Downloader) UpdateChecksum(opts UpdateChecksumOpts) error { return nil } -//InstallOpts options for Install -type InstallOpts struct { - // DownloaderName is the downloader's key from the config file - DownloaderName string - // CellarDir is the directory where downloads and extractions will be placed. Default is a /.bindown - CellarDir string - // TargetDir is the directory where the executable should end up - TargetDir string - // Force - whether to force the install even if it already exists - Force bool -} - func (d *Downloader) downloadsSubName() string { - return mustHexHash(fnv.New64a(), []byte(d.Checksum)) -} - -func (d *Downloader) extractsSubName() string { - return mustHexHash(fnv.New64a(), []byte(d.Checksum), []byte(d.BinName)) + return util.MustHexHash(fnv.New64a(), []byte(d.Checksum)) } //Install downloads and installs a bin -func (d *Downloader) Install(opts InstallOpts) error { - d.setDefaultBinName(opts.DownloaderName) - cellarDir := opts.CellarDir - if cellarDir == "" { - cellarDir = filepath.Join(opts.TargetDir, ".bindown") +func (d *Downloader) Install(downloaderName, cellarDir, targetDir string, force bool) error { + binName := d.BinName + if binName == "" { + binName = downloaderName } + target := filepath.Join(targetDir, binName) + if cellarDir == "" { + cellarDir = filepath.Join(targetDir, ".bindown") + } downloadDir := filepath.Join(cellarDir, "downloads", d.downloadsSubName()) - extractDir := filepath.Join(cellarDir, "extracts", d.extractsSubName()) + extractDir := filepath.Join(cellarDir, "extracts", binName) - if opts.Force { + if force { err := os.RemoveAll(downloadDir) if err != nil { return err @@ -257,32 +255,27 @@ func (d *Downloader) Install(opts InstallOpts) error { err := d.download(downloadDir) if err != nil { - log.Printf("error downloading: %v", err) - return err + return fmt.Errorf("downloading: %v", err) } err = d.validateChecksum(downloadDir) if err != nil { - log.Printf("error validating: %v", err) - return err + return fmt.Errorf("validating: %v", err) } err = d.extract(downloadDir, extractDir) if err != nil { - log.Printf("error extracting: %v", err) - return err + return fmt.Errorf("extracting: %v", err) } - err = d.moveOrLinkBin(opts.TargetDir, extractDir) + err = d.moveOrLinkBin(target, extractDir) if err != nil { - log.Printf("error moving: %v", err) - return err + return fmt.Errorf("moving: %v", err) } - err = d.chmod(opts.TargetDir) + err = os.Chmod(target, 0755) //nolint:gosec if err != nil { - log.Printf("error chmodding: %v", err) - return err + return fmt.Errorf("chmodding: %v", err) } return nil @@ -293,7 +286,7 @@ func downloadFile(targetPath, url string) error { if err != nil { return err } - defer logCloseErr(resp.Body) + defer util.LogCloseErr(resp.Body) if resp.StatusCode >= 300 { return fmt.Errorf("failed downloading %s", url) } @@ -301,7 +294,46 @@ func downloadFile(targetPath, url string) error { if err != nil { return err } - defer logCloseErr(out) + defer util.LogCloseErr(out) _, err = io.Copy(out, resp.Body) return err } + +//Validate attempts a download to a temp location to validate the download will work as configured. +func (d *Downloader) Validate(cellarDir string) error { + tmpDir, tmpTeardown, err := util.TmpDir() + if err != nil { + return err + } + defer tmpTeardown() + + binDir := filepath.Join(tmpDir, "bin") + err = os.MkdirAll(binDir, 0700) + if err != nil { + return err + } + + if cellarDir == "" { + cellarDir = filepath.Join(tmpDir, "cellar") + } + + return d.Install(d.BinName, cellarDir, binDir, true) +} + +func eqOS(a, b string) bool { + return strings.EqualFold(normalizeOS(a), normalizeOS(b)) +} + +var osAliases = map[string]string{ + "osx": "darwin", + "macos": "darwin", +} + +func normalizeOS(os string) string { + for alias, aliasedOs := range osAliases { + if strings.EqualFold(alias, os) { + return aliasedOs + } + } + return strings.ToLower(os) +} diff --git a/downloader_test.go b/downloader_test.go index 7c8d5ff..5f30554 100644 --- a/downloader_test.go +++ b/downloader_test.go @@ -1,49 +1,52 @@ package bindown import ( + "fmt" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/willabides/bindown/v2/internal/testhelper" + "github.com/willabides/bindown/v2/internal/util" ) func mustCopyFile(t *testing.T, src, dst string) { t.Helper() require.NoError(t, os.MkdirAll(filepath.Dir(dst), 0750)) - require.NoError(t, copyFile(src, dst)) + require.NoError(t, util.CopyFile(src, dst)) } func Test_downloadFile(t *testing.T) { t.Run("success", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() - ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "") + ts := testhelper.ServeFile(testhelper.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, downloadablesPath("foo.tar.gz"), filepath.Join(dir, "bar.tar.gz")) + testhelper.AssertEqualFiles(t, testhelper.DownloadablesPath("foo.tar.gz"), filepath.Join(dir, "bar.tar.gz")) }) t.Run("404", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() - ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "") + ts := testhelper.ServeFile(testhelper.DownloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "") err := downloadFile(filepath.Join(dir, "bar.tar.gz"), ts.URL+"/wrongpath") assert.Error(t, err) }) t.Run("bad url", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() err := downloadFile(filepath.Join(dir, "bar.tar.gz"), "https://bad/url") assert.Error(t, err) }) t.Run("bad target", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() - ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "") + ts := testhelper.ServeFile(testhelper.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) }) @@ -51,20 +54,20 @@ func Test_downloadFile(t *testing.T) { func Test_downloader_validateChecksum(t *testing.T) { t.Run("success", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() d := &Downloader{ URL: "foo/foo.tar.gz", Checksum: "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88", } - mustCopyFile(t, downloadablesPath("foo.tar.gz"), filepath.Join(dir, "foo.tar.gz")) + mustCopyFile(t, testhelper.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"))) + assert.True(t, util.FileExists(filepath.Join(dir, "foo.tar.gz"))) }) t.Run("missing file", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() d := &Downloader{ URL: "foo/foo.tar.gz", @@ -76,21 +79,21 @@ func Test_downloader_validateChecksum(t *testing.T) { }) t.Run("mismatch", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() d := &Downloader{ URL: "foo/foo.tar.gz", Checksum: "deadbeef", } - mustCopyFile(t, downloadablesPath("foo.tar.gz"), filepath.Join(dir, "foo.tar.gz")) + mustCopyFile(t, testhelper.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"))) + assert.False(t, util.FileExists(filepath.Join(dir, "foo.tar.gz"))) }) } func TestDownloader_extract(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() d := &Downloader{ URL: "foo/foo.tar.gz", @@ -98,17 +101,123 @@ func TestDownloader_extract(t *testing.T) { } downloadDir := filepath.Join(dir, "download") extractDir := filepath.Join(dir, "extract") - mustCopyFile(t, downloadablesPath("foo.tar.gz"), filepath.Join(downloadDir, "foo.tar.gz")) + mustCopyFile(t, testhelper.DownloadablesPath("foo.tar.gz"), filepath.Join(downloadDir, "foo.tar.gz")) err := d.extract(downloadDir, extractDir) assert.NoError(t, err) } +func TestDownloader_Validate(t *testing.T) { + t.Run("raw file", func(t *testing.T) { + servePath := testhelper.DownloadablesPath("rawfile/foo") + ts := testhelper.ServeFile(servePath, "/foo/foo", "") + d := &Downloader{ + URL: ts.URL + "/foo/foo", + Checksum: "f044ff8b6007c74bcc1b5a5c92776e5d49d6014f5ff2d551fab115c17f48ac41", + BinName: "foo", + Arch: "amd64", + OS: "darwin", + } + err := d.Validate("") + assert.NoError(t, err) + }) + + t.Run("bin in root", func(t *testing.T) { + servePath := testhelper.DownloadablesPath("fooinroot.tar.gz") + ts := testhelper.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.Validate("") + assert.NoError(t, err) + }) + + t.Run("move", func(t *testing.T) { + ts := testhelper.ServeFile(testhelper.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.Validate("") + assert.NoError(t, err) + }) + + t.Run("link", func(t *testing.T) { + ts := testhelper.ServeFile(testhelper.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.Validate("") + assert.NoError(t, err) + }) + + t.Run("download error", func(t *testing.T) { + ts := testhelper.ServeFile(testhelper.DownloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar") + u := ts.URL + "/foo/wrongpath" + d := &Downloader{ + URL: u, + Checksum: "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88", + BinName: "foo", + ArchivePath: "bin/foo.txt", + Link: true, + Arch: "amd64", + OS: "darwin", + } + err := d.Validate("") + wantErr := fmt.Sprintf("downloading: failed downloading %s", u) + assert.Error(t, err) + assert.Equal(t, wantErr, err.Error()) + }) + + t.Run("checksum error", func(t *testing.T) { + ts := testhelper.ServeFile(testhelper.DownloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar") + d := &Downloader{ + URL: ts.URL + "/foo/foo.tar.gz?foo=bar", + Checksum: "deadbeef", + BinName: "foo", + ArchivePath: "bin/foo.txt", + Link: true, + Arch: "amd64", + OS: "darwin", + } + err := d.Validate("") + assert.Error(t, err) + }) + + t.Run("wrong archivepath", func(t *testing.T) { + ts := testhelper.ServeFile(testhelper.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/wrong", + Arch: "amd64", + OS: "darwin", + } + err := d.Validate("") + assert.Error(t, err) + }) +} + func TestDownloader_Install(t *testing.T) { t.Run("raw file", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() - servePath := downloadablesPath("rawfile/foo") - ts := serveFile(servePath, "/foo/foo", "") + servePath := testhelper.DownloadablesPath("rawfile/foo") + ts := testhelper.ServeFile(servePath, "/foo/foo", "") d := &Downloader{ URL: ts.URL + "/foo/foo", Checksum: "f044ff8b6007c74bcc1b5a5c92776e5d49d6014f5ff2d551fab115c17f48ac41", @@ -116,12 +225,9 @@ func TestDownloader_Install(t *testing.T) { Arch: "amd64", OS: "darwin", } - err := d.Install(InstallOpts{ - TargetDir: dir, - Force: true, - }) + err := d.Install("", "", dir, true) assert.NoError(t, err) - assert.True(t, fileExists(filepath.Join(dir, "foo"))) + assert.True(t, util.FileExists(filepath.Join(dir, "foo"))) stat, err := os.Stat(filepath.Join(dir, "foo")) assert.NoError(t, err) assert.False(t, stat.IsDir()) @@ -129,10 +235,10 @@ func TestDownloader_Install(t *testing.T) { }) t.Run("bin in root", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() - servePath := downloadablesPath("fooinroot.tar.gz") - ts := serveFile(servePath, "/foo/fooinroot.tar.gz", "") + servePath := testhelper.DownloadablesPath("fooinroot.tar.gz") + ts := testhelper.ServeFile(servePath, "/foo/fooinroot.tar.gz", "") d := &Downloader{ URL: ts.URL + "/foo/fooinroot.tar.gz", Checksum: "27dcce60d1ed72920a84dd4bc01e0bbd013e5a841660e9ee2e964e53fb83c0b3", @@ -140,12 +246,9 @@ func TestDownloader_Install(t *testing.T) { Arch: "amd64", OS: "darwin", } - err := d.Install(InstallOpts{ - TargetDir: dir, - Force: true, - }) + err := d.Install("", "", dir, true) assert.NoError(t, err) - assert.True(t, fileExists(filepath.Join(dir, "foo"))) + assert.True(t, util.FileExists(filepath.Join(dir, "foo"))) stat, err := os.Stat(filepath.Join(dir, "foo")) assert.NoError(t, err) assert.False(t, stat.IsDir()) @@ -153,9 +256,9 @@ func TestDownloader_Install(t *testing.T) { }) t.Run("move", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() - ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar") + ts := testhelper.ServeFile(testhelper.DownloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar") d := &Downloader{ URL: ts.URL + "/foo/foo.tar.gz?foo=bar", Checksum: "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88", @@ -164,17 +267,14 @@ func TestDownloader_Install(t *testing.T) { Arch: "amd64", OS: "darwin", } - err := d.Install(InstallOpts{ - TargetDir: dir, - Force: true, - }) + err := d.Install("", "", dir, true) assert.NoError(t, err) }) t.Run("legacy MoveFrom", func(t *testing.T) { - dir, teardown := tmpDir(t) + dir, teardown := testhelper.TmpDir(t) defer teardown() - ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar") + ts := testhelper.ServeFile(testhelper.DownloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar") d := &Downloader{ URL: ts.URL + "/foo/foo.tar.gz?foo=bar", Checksum: "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88", @@ -183,17 +283,14 @@ func TestDownloader_Install(t *testing.T) { Arch: "amd64", OS: "darwin", } - err := d.Install(InstallOpts{ - TargetDir: dir, - Force: true, - }) + err := d.Install("", "", dir, true) assert.NoError(t, err) }) - t.Run("link", func(t *testing.T) { - dir, teardown := tmpDir(t) + t.Run("legacy LinkSource", func(t *testing.T) { + dir, teardown := testhelper.TmpDir(t) defer teardown() - ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar") + ts := testhelper.ServeFile(testhelper.DownloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar") d := &Downloader{ URL: ts.URL + "/foo/foo.tar.gz?foo=bar", Checksum: "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88", @@ -202,21 +299,18 @@ func TestDownloader_Install(t *testing.T) { Arch: "amd64", OS: "darwin", } - err := d.Install(InstallOpts{ - TargetDir: dir, - Force: true, - }) + err := d.Install("", "", dir, 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)) + assert.True(t, util.FileExists(absLinkTo)) }) - t.Run("legacy LinkSource", func(t *testing.T) { - dir, teardown := tmpDir(t) + t.Run("link", func(t *testing.T) { + dir, teardown := testhelper.TmpDir(t) defer teardown() - ts := serveFile(downloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar") + ts := testhelper.ServeFile(testhelper.DownloadablesPath("foo.tar.gz"), "/foo/foo.tar.gz", "foo=bar") d := &Downloader{ URL: ts.URL + "/foo/foo.tar.gz?foo=bar", Checksum: "f7fa712caea646575c920af17de3462fe9d08d7fe062b9a17010117d5fa4ed88", @@ -226,14 +320,11 @@ func TestDownloader_Install(t *testing.T) { Arch: "amd64", OS: "darwin", } - err := d.Install(InstallOpts{ - TargetDir: dir, - Force: true, - }) + err := d.Install("", "", dir, 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)) + assert.True(t, util.FileExists(absLinkTo)) }) } diff --git a/go.mod b/go.mod index 2d4ae5e..9bd0832 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,13 @@ require ( github.com/alecthomas/kong v0.2.1 github.com/andybalholm/brotli v1.0.0 // indirect github.com/frankban/quicktest v1.4.2 // indirect + github.com/golang/mock v1.3.1 github.com/mholt/archiver/v3 v3.3.0 github.com/pierrec/lz4 v2.3.0+incompatible // indirect github.com/stretchr/testify v1.4.0 github.com/udhos/equalfile v0.3.0 + go.uber.org/atomic v1.5.1 // indirect + go.uber.org/multierr v1.4.0 + golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect + golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9 // indirect ) diff --git a/go.sum b/go.sum index fbaf1f1..5790cfe 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/alecthomas/kong v0.2.1 h1:V1tLBhyQBC4rsbXbcOvm3GBaytJSwRNX69fp1WJxbqQ= github.com/alecthomas/kong v0.2.1/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= @@ -12,10 +14,14 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf github.com/frankban/quicktest v1.4.2 h1:eV8n2LQHuA97qKj0t6+7UrHRU0Smz9G+yh87F3Z+3Uk= github.com/frankban/quicktest v1.4.2/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.2 h1:LfVyl+ZlLlLDeQ/d2AqfGIIH4qEDu0Ed2S5GyhCWIWY= github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -39,8 +45,10 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/udhos/equalfile v0.3.0 h1:KhG4xhhkittrgIV/ekHtpEPh7MLxtbjm6kLEwp5Dlbg= @@ -49,7 +57,42 @@ github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9 h1:m9xhlkk2j+sO9WjAgNfTtl505MN7ZkuW69nOcBlp9qY= +golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/testhelper_test.go b/internal/testhelper/testhelper.go similarity index 53% rename from testhelper_test.go rename to internal/testhelper/testhelper.go index ac0374c..854d7d4 100644 --- a/testhelper_test.go +++ b/internal/testhelper/testhelper.go @@ -1,4 +1,4 @@ -package bindown +package testhelper import ( "io/ioutil" @@ -14,25 +14,21 @@ import ( "github.com/udhos/equalfile" ) -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 { + return filepath.Join(ProjectRoot(), filepath.Join(path...)) } -// projectPath exchanges a path relative to the project root for an absolute path -func projectPath(path ...string) string { - return filepath.Join(projectRoot(), filepath.Join(path...)) -} - -// projectRoot returns the absolute path of the project root -func projectRoot() string { +// ProjectRoot returns the absolute path of the project root +func ProjectRoot() string { _, file, _, _ := runtime.Caller(0) - return filepath.Dir(file) + return filepath.Join(filepath.Dir(file), "..", "..") } -// tmpDir returns the path to a newly created tmp dir and a function for deleting that dir -func tmpDir(t *testing.T) (string, func()) { +// TmpDir returns the path to a newly created tmp dir and a function for deleting that dir +func TmpDir(t *testing.T) (string, func()) { t.Helper() - projectTmp := projectPath("tmp") + projectTmp := ProjectPath("tmp") err := os.MkdirAll(projectTmp, 0750) require.NoError(t, err) tmpdir, err := ioutil.TempDir(projectTmp, "") @@ -42,7 +38,13 @@ func tmpDir(t *testing.T) (string, func()) { } } -func serveFile(file, path, query string) *httptest.Server { +//DownloadablesPath path to a file or directory in testdata/downloadables +func DownloadablesPath(path string) string { + return filepath.Join(ProjectPath("testdata", "downloadables"), filepath.FromSlash(path)) +} + +//ServeFile runs a test server +func ServeFile(file, path, query string) *httptest.Server { file = filepath.FromSlash(file) mux := http.NewServeMux() mux.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) { @@ -55,7 +57,8 @@ func serveFile(file, path, query string) *httptest.Server { return httptest.NewServer(mux) } -func assertEqualFiles(t testing.TB, want, actual string) bool { +//AssertEqualFiles asserts that files at want and actual have the same content +func AssertEqualFiles(t testing.TB, want, actual string) bool { t.Helper() cmp := equalfile.New(nil, equalfile.Options{}) equal, err := cmp.CompareFile(want, actual) diff --git a/util.go b/internal/util/util.go similarity index 53% rename from util.go rename to internal/util/util.go index c5a86d4..78d5d61 100644 --- a/util.go +++ b/internal/util/util.go @@ -1,4 +1,4 @@ -package bindown +package util import ( "crypto/sha256" @@ -12,26 +12,47 @@ import ( "path/filepath" ) -func logCloseErr(closer io.Closer) { +//TmpDir returns a temp dir and a function to delete it +func TmpDir() (string, func(), error) { + tmpDir, err := ioutil.TempDir("", "bindown") + if err != nil { + return "", func() {}, err + } + return tmpDir, func() { + _ = Rm(tmpDir) //nolint:errcheck + }, nil +} + +//Rm removes a file and filters out IsNotExist errors +func Rm(path string) error { + err := os.RemoveAll(path) + if err == nil || os.IsNotExist(err) { + return nil + } + return fmt.Errorf(`failed to remove %s: %v`, path, err) +} + +//LogCloseErr logs errors closing closers. Useful in defer statements. +func LogCloseErr(closer io.Closer) { err := closer.Close() if err != nil { log.Println(err) } } -// mustHexHash is like hexHash but panics on err +// MustHexHash is like hexHash but panics on err // this should only be used with hashers that are guaranteed to return a nil error from Write() -func mustHexHash(hasher hash.Hash, data ...[]byte) string { - hsh, err := hexHash(hasher, data...) +func MustHexHash(hasher hash.Hash, data ...[]byte) string { + hsh, err := HexHash(hasher, data...) if err != nil { panic(err) } return hsh } -// hexHash returns a hex representation of data's hash +// HexHash returns a hex representation of data's hash // This will only return non-nil error if given a hasher that can return a non-nil error from Write() -func hexHash(hasher hash.Hash, data ...[]byte) (string, error) { +func HexHash(hasher hash.Hash, data ...[]byte) (string, error) { hasher.Reset() for _, datum := range data { _, err := hasher.Write(datum) @@ -42,36 +63,37 @@ func hexHash(hasher hash.Hash, data ...[]byte) (string, error) { return hex.EncodeToString(hasher.Sum(nil)), nil } -// fileChecksum returns the hex checksum of a file -func fileChecksum(filename string) (string, error) { +// FileChecksum returns the hex checksum of a file +func FileChecksum(filename string) (string, error) { fileBytes, err := ioutil.ReadFile(filename) //nolint:gosec if err != nil { return "", err } - return mustHexHash(sha256.New(), fileBytes), nil + return MustHexHash(sha256.New(), fileBytes), nil } -//fileExistsWithChecksum returns true if the file both exists and has a matching checksum -func fileExistsWithChecksum(filename, checksum string) (bool, error) { - if !fileExists(filename) { +//FileExistsWithChecksum returns true if the file both exists and has a matching checksum +func FileExistsWithChecksum(filename, checksum string) (bool, error) { + if !FileExists(filename) { return false, nil } - got, err := fileChecksum(filename) + got, err := FileChecksum(filename) if err != nil { return false, err } return checksum == got, nil } -//fileExists asserts that a file exists -func fileExists(path string) bool { +//FileExists asserts that a file exists +func FileExists(path string) bool { if _, err := os.Stat(filepath.FromSlash(path)); !os.IsNotExist(err) { return true } return false } -func copyFile(src, dst string) error { +//CopyFile copys a file from src to dst +func CopyFile(src, dst string) error { srcStat, err := os.Stat(src) if err != nil { return err @@ -84,22 +106,14 @@ func copyFile(src, dst string) error { if err != nil { return err } - defer logCloseErr(rdr) + defer LogCloseErr(rdr) writer, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcStat.Mode()) if err != nil { return err } - defer logCloseErr(writer) + defer LogCloseErr(writer) _, err = io.Copy(writer, rdr) return err } - -func rm(path string) error { - err := os.RemoveAll(path) - if err == nil || os.IsNotExist(err) { - return nil - } - return fmt.Errorf(`failed to remove %s: %v`, path, err) -} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..92ac4ba --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,169 @@ +package config + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + + "github.com/willabides/bindown/v2" + "github.com/willabides/bindown/v2/internal/util" + "go.uber.org/multierr" +) + +//go:generate mockgen -source config.go -destination internal/mocks/mock_config.go -package mocks + +//Downloader interface for *bindown.Downloader +type Downloader interface { + ErrString(binary string) string + MatchesOS(opSys string) bool + MatchesArch(arch string) bool + HasChecksum(checksum string) bool + UpdateChecksum(cellarDir string) error + Install(downloaderName, cellarDir, targetDir string, force bool) error + Validate(cellarDir string) error +} + +func unmarshalDownloaders(p []byte) (map[string][]*bindown.Downloader, error) { + downloaders := map[string][]*bindown.Downloader{} + decoder := json.NewDecoder(bytes.NewReader(p)) + decoder.DisallowUnknownFields() + err := decoder.Decode(&downloaders) + if err != nil { + return nil, err + } + return downloaders, nil +} + +//LoadConfig returns a Config from a config reader +func LoadConfig(config io.Reader) (*Config, error) { + var cfg Config + err := json.NewDecoder(config).Decode(&cfg) + return &cfg, err +} + +//Config is downloaders configuration +type Config struct { + Downloaders map[string][]Downloader `json:"downloaders,omitempty"` +} + +func downloadersToInterface(dls map[string][]*bindown.Downloader) map[string][]Downloader { + result := make(map[string][]Downloader, len(dls)) + for key, downloaders := range dls { + result[key] = make([]Downloader, len(downloaders)) + for i, downloader := range downloaders { + result[key][i] = downloader + } + } + return result +} + +//UnmarshalJSON implements json.Unmarshaler +func (c *Config) UnmarshalJSON(p []byte) error { + jsm := struct { + Downloaders map[string][]*bindown.Downloader `json:"downloaders,omitempty"` + }{} + decoder := json.NewDecoder(bytes.NewReader(p)) + decoder.DisallowUnknownFields() + err := decoder.Decode(&jsm) + if err == nil { + c.Downloaders = downloadersToInterface(jsm.Downloaders) + return nil + } + dls, err := unmarshalDownloaders(p) + if err != nil { + return err + } + c.Downloaders = downloadersToInterface(dls) + return nil +} + +// Downloader returns a Downloader for the given binary, os and arch. +func (c *Config) Downloader(binary, opSys, arch string) Downloader { + l, ok := c.Downloaders[binary] + if !ok { + return nil + } + for _, d := range l { + if d.MatchesOS(opSys) && d.MatchesArch(arch) { + return d + } + } + return nil +} + +//UpdateChecksums updates the checksums for binary's downloaders +func (c *Config) UpdateChecksums(binary, cellarDir string) error { + if len(c.Downloaders[binary]) == 0 { + return fmt.Errorf("nothing configured for binary %q", binary) + } + var err error + if cellarDir == "" { + cellarDir, err = ioutil.TempDir("", "bindown") + if err != nil { + return err + } + defer func() { + _ = util.Rm(cellarDir) //nolint:errcheck + }() + } + for _, downloader := range c.Downloaders[binary] { + err = downloader.UpdateChecksum(cellarDir) + if err != nil { + return err + } + } + return nil +} + +//Validate runs validate on all Downloaders for the given binary. +//error may be a multierr. Individual errors can be retrieved with multierr.Errors(err) +func (c *Config) Validate(binary, cellarDir string) error { + downloaders := c.Downloaders[binary] + if len(downloaders) == 0 { + return fmt.Errorf("nothing configured for binary %q", binary) + } + var resErr error + for _, downloader := range downloaders { + err := downloader.Validate(cellarDir) + if err != nil { + resErr = multierr.Combine( + resErr, + fmt.Errorf("error validating %s: %v", downloader.ErrString(binary), err), + ) + } + } + return resErr +} + +//File represents a config file +type File struct { + filename string + *Config +} + +//NewConfigFile creates a *File for the file at filename +func NewConfigFile(filename string) (*File, error) { + b, err := ioutil.ReadFile(filename) //nolint:gosec + if err != nil { + return nil, fmt.Errorf("couldn't read config file: %s", filename) + } + config, err := LoadConfig(bytes.NewReader(b)) + if err != nil { + return nil, fmt.Errorf("couldn't load config: %v", err) + } + return &File{ + filename: filename, + Config: config, + }, nil +} + +//WriteFile writes config back to the file +func (c *File) WriteFile() error { + b, err := json.MarshalIndent(c.Config, "", " ") + if err != nil { + return err + } + return ioutil.WriteFile(c.filename, b, 0640) +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go new file mode 100644 index 0000000..a19db79 --- /dev/null +++ b/pkg/config/config_test.go @@ -0,0 +1,177 @@ +package config + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/willabides/bindown/v2/internal/testhelper" + "github.com/willabides/bindown/v2/pkg/config/internal/mocks" + "go.uber.org/multierr" +) + +func TestConfig_Validate(t *testing.T) { + t.Run("success", func(t *testing.T) { + cellarDir := "cellar" + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDownloader := mocks.NewMockDownloader(ctrl) + mockDownloader.EXPECT().Validate(cellarDir) + mockDownloader2 := mocks.NewMockDownloader(ctrl) + mockDownloader2.EXPECT().Validate(cellarDir) + downloaders := map[string][]Downloader{ + "foo": {mockDownloader, mockDownloader2}, + } + cfg := &Config{ + Downloaders: downloaders, + } + err := cfg.Validate("foo", cellarDir) + assert.NoError(t, err) + }) + + t.Run("errs", func(t *testing.T) { + err1 := fmt.Errorf("err1") + err2 := fmt.Errorf("err2") + cellarDir := "cellar" + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDownloader := mocks.NewMockDownloader(ctrl) + mockDownloader.EXPECT().Validate(cellarDir).Return(err1) + mockDownloader.EXPECT().ErrString("foo").Return("errmsg1") + mockDownloader2 := mocks.NewMockDownloader(ctrl) + mockDownloader2.EXPECT().Validate(cellarDir) + mockDownloader3 := mocks.NewMockDownloader(ctrl) + mockDownloader3.EXPECT().Validate(cellarDir).Return(err2) + mockDownloader3.EXPECT().ErrString("foo").Return("errmsg2") + downloaders := map[string][]Downloader{ + "foo": {mockDownloader, mockDownloader2, mockDownloader3}, + } + cfg := &Config{ + Downloaders: downloaders, + } + err := cfg.Validate("foo", cellarDir) + assert.Error(t, err) + errs := multierr.Errors(err) + assert.Len(t, errs, 2) + assert.Equal(t, "error validating errmsg1: err1", errs[0].Error()) + assert.Equal(t, "error validating errmsg2: err2", errs[1].Error()) + }) +} + +func TestUpdateChecksums(t *testing.T) { + t.Run("success", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDownloader := mocks.NewMockDownloader(ctrl) + mockDownloader.EXPECT().UpdateChecksum(gomock.Any()) + mockDownloader2 := mocks.NewMockDownloader(ctrl) + mockDownloader2.EXPECT().UpdateChecksum(gomock.Any()) + downloaders := map[string][]Downloader{ + "foo": {mockDownloader, mockDownloader2}, + } + cfg := &Config{ + Downloaders: downloaders, + } + err := cfg.UpdateChecksums("foo", "") + assert.NoError(t, err) + }) + + t.Run("errs", func(t *testing.T) { + cellarDir := "cellar" + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockDownloader := mocks.NewMockDownloader(ctrl) + mockDownloader.EXPECT().UpdateChecksum(cellarDir) + mockDownloader2 := mocks.NewMockDownloader(ctrl) + mockDownloader2.EXPECT().UpdateChecksum(cellarDir) + mockDownloader3 := mocks.NewMockDownloader(ctrl) + mockDownloader3.EXPECT().UpdateChecksum(cellarDir).Return(assert.AnError) + downloaders := map[string][]Downloader{ + "foo": {mockDownloader, mockDownloader2, mockDownloader3}, + } + cfg := &Config{ + Downloaders: downloaders, + } + err := cfg.UpdateChecksums("foo", cellarDir) + assert.Error(t, err) + assert.Equal(t, assert.AnError, err) + }) +} + +func TestNewConfigFile(t *testing.T) { + t.Run("current format", func(t *testing.T) { + filename := testhelper.ProjectPath(filepath.FromSlash("testdata/config/ex1.json")) + configFile, err := NewConfigFile(filename) + assert.NoError(t, err) + assert.True(t, configFile.Downloaders["gobin"][0]. + HasChecksum("84ed966949e06bebd7d006bc343caf9d736932fd8b37df5cb5b268a28d07bd30")) + }) + + t.Run("old format", func(t *testing.T) { + filename := testhelper.ProjectPath(filepath.FromSlash("testdata/config/oldformat.json")) + configFile, err := NewConfigFile(filename) + assert.NoError(t, err) + assert.True(t, configFile.Downloaders["gobin"][0]. + HasChecksum("84ed966949e06bebd7d006bc343caf9d736932fd8b37df5cb5b268a28d07bd30")) + }) + + t.Run("unknown field", func(t *testing.T) { + filename := testhelper.ProjectPath(filepath.FromSlash("testdata/config/unknownfield.json")) + _, err := NewConfigFile(filename) + assert.Error(t, err) + }) + + t.Run("file does not exist", func(t *testing.T) { + filename := testhelper.ProjectPath(filepath.FromSlash("testdata/config/fakefile.json")) + _, err := NewConfigFile(filename) + assert.Error(t, err) + }) +} + +func TestConfig_Downloader(t *testing.T) { + configFile, err := NewConfigFile(testhelper.ProjectPath(filepath.FromSlash("testdata/config/ex1.json"))) + require.NoError(t, err) + config := configFile.Config + + t.Run("success", func(t *testing.T) { + dl := config.Downloader("gobin", "linux", "amd64") + assert.NotNil(t, dl) + assert.True(t, dl.HasChecksum("415266d9af98578067051653f5057ea267c51ebf085408df48b118a8b978bac6")) + }) + + t.Run("no mapped bin", func(t *testing.T) { + dl := config.Downloader("foo", "darwin", "amd64") + assert.Nil(t, dl) + }) + + t.Run("missing os", func(t *testing.T) { + dl := config.Downloader("gobin", "windows", "amd64") + assert.Nil(t, dl) + }) + + t.Run("missing arch", func(t *testing.T) { + dl := config.Downloader("gobin", "darwin", "x86") + assert.Nil(t, dl) + }) +} + +func TestConfigFile_WriteFile(t *testing.T) { + filename := testhelper.ProjectPath(filepath.FromSlash("testdata/config/ex1.json")) + wantBytes, err := ioutil.ReadFile(filename) + require.NoError(t, err) + configFile, err := NewConfigFile(filename) + require.NoError(t, err) + tmpDir, teardown := testhelper.TmpDir(t) + defer teardown() + newFile := filepath.Join(tmpDir, "config.json") + configFile.filename = newFile + err = configFile.WriteFile() + assert.NoError(t, err) + gotBytes, err := ioutil.ReadFile(newFile) + require.NoError(t, err) + assert.JSONEq(t, string(wantBytes), string(gotBytes)) +} diff --git a/pkg/config/internal/mocks/mock_config.go b/pkg/config/internal/mocks/mock_config.go new file mode 100644 index 0000000..acb9b2b --- /dev/null +++ b/pkg/config/internal/mocks/mock_config.go @@ -0,0 +1,131 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: config.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockDownloader is a mock of Downloader interface +type MockDownloader struct { + ctrl *gomock.Controller + recorder *MockDownloaderMockRecorder +} + +// MockDownloaderMockRecorder is the mock recorder for MockDownloader +type MockDownloaderMockRecorder struct { + mock *MockDownloader +} + +// NewMockDownloader creates a new mock instance +func NewMockDownloader(ctrl *gomock.Controller) *MockDownloader { + mock := &MockDownloader{ctrl: ctrl} + mock.recorder = &MockDownloaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockDownloader) EXPECT() *MockDownloaderMockRecorder { + return m.recorder +} + +// ErrString mocks base method +func (m *MockDownloader) ErrString(binary string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ErrString", binary) + ret0, _ := ret[0].(string) + return ret0 +} + +// ErrString indicates an expected call of ErrString +func (mr *MockDownloaderMockRecorder) ErrString(binary interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ErrString", reflect.TypeOf((*MockDownloader)(nil).ErrString), binary) +} + +// MatchesOS mocks base method +func (m *MockDownloader) MatchesOS(opSys string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MatchesOS", opSys) + ret0, _ := ret[0].(bool) + return ret0 +} + +// MatchesOS indicates an expected call of MatchesOS +func (mr *MockDownloaderMockRecorder) MatchesOS(opSys interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MatchesOS", reflect.TypeOf((*MockDownloader)(nil).MatchesOS), opSys) +} + +// MatchesArch mocks base method +func (m *MockDownloader) MatchesArch(arch string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MatchesArch", arch) + ret0, _ := ret[0].(bool) + return ret0 +} + +// MatchesArch indicates an expected call of MatchesArch +func (mr *MockDownloaderMockRecorder) MatchesArch(arch interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MatchesArch", reflect.TypeOf((*MockDownloader)(nil).MatchesArch), arch) +} + +// HasChecksum mocks base method +func (m *MockDownloader) HasChecksum(checksum string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasChecksum", checksum) + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasChecksum indicates an expected call of HasChecksum +func (mr *MockDownloaderMockRecorder) HasChecksum(checksum interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasChecksum", reflect.TypeOf((*MockDownloader)(nil).HasChecksum), checksum) +} + +// UpdateChecksum mocks base method +func (m *MockDownloader) UpdateChecksum(cellarDir string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateChecksum", cellarDir) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateChecksum indicates an expected call of UpdateChecksum +func (mr *MockDownloaderMockRecorder) UpdateChecksum(cellarDir interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChecksum", reflect.TypeOf((*MockDownloader)(nil).UpdateChecksum), cellarDir) +} + +// Install mocks base method +func (m *MockDownloader) Install(downloaderName, cellarDir, targetDir string, force bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Install", downloaderName, cellarDir, targetDir, force) + ret0, _ := ret[0].(error) + return ret0 +} + +// Install indicates an expected call of Install +func (mr *MockDownloaderMockRecorder) Install(downloaderName, cellarDir, targetDir, force interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Install", reflect.TypeOf((*MockDownloader)(nil).Install), downloaderName, cellarDir, targetDir, force) +} + +// Validate mocks base method +func (m *MockDownloader) Validate(cellarDir string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validate", cellarDir) + ret0, _ := ret[0].(error) + return ret0 +} + +// Validate indicates an expected call of Validate +func (mr *MockDownloaderMockRecorder) Validate(cellarDir interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockDownloader)(nil).Validate), cellarDir) +} diff --git a/script/fmt b/script/fmt index 7ad33c5..0fc4ed4 100755 --- a/script/fmt +++ b/script/fmt @@ -4,6 +4,6 @@ set -e CDPATH="" cd -- "$(dirname -- "$(dirname -- "$0")")" -[ -f bin/goimports ] || make -s bin/goimports +[ -f bin/golangci-lint ] || make -s bin/golangci-lint -bin/goimports -w . +bin/golangci-lint run --disable-all --no-config -E goimports --fix diff --git a/script/generate b/script/generate new file mode 100755 index 0000000..8244280 --- /dev/null +++ b/script/generate @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e + +CDPATH="" cd -- "$(dirname -- "$(dirname -- "$0")")" + +[ -f bin/mockgen ] || make -s bin/mockgen + +PATH="$(pwd)/bin:$PATH" +go generate ./... diff --git a/testdata/config/ex1.json b/testdata/config/ex1.json new file mode 100644 index 0000000..c28197c --- /dev/null +++ b/testdata/config/ex1.json @@ -0,0 +1,21 @@ +{ + "downloaders": { + "gobin": [ + { + "os": "darwin", + "arch": "amd64", + "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/darwin-amd64", + "checksum": "84ed966949e06bebd7d006bc343caf9d736932fd8b37df5cb5b268a28d07bd30", + "archive_path": "darwin-amd64", + "link": true + }, + { + "os": "linux", + "arch": "amd64", + "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/linux-amd64", + "checksum": "415266d9af98578067051653f5057ea267c51ebf085408df48b118a8b978bac6", + "archive_path": "linux-amd64" + } + ] + } +} diff --git a/testdata/config/oldformat.json b/testdata/config/oldformat.json new file mode 100644 index 0000000..18418e3 --- /dev/null +++ b/testdata/config/oldformat.json @@ -0,0 +1,19 @@ +{ + "gobin": [ + { + "os": "darwin", + "arch": "amd64", + "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/darwin-amd64", + "checksum": "84ed966949e06bebd7d006bc343caf9d736932fd8b37df5cb5b268a28d07bd30", + "archive_path": "darwin-amd64", + "link": true + }, + { + "os": "linux", + "arch": "amd64", + "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/linux-amd64", + "checksum": "415266d9af98578067051653f5057ea267c51ebf085408df48b118a8b978bac6", + "archive_path": "linux-amd64" + } + ] +} diff --git a/testdata/config/unknownfield.json b/testdata/config/unknownfield.json new file mode 100644 index 0000000..02f9fcb --- /dev/null +++ b/testdata/config/unknownfield.json @@ -0,0 +1,22 @@ +{ + "foo": "bar", + "downloaders": { + "gobin": [ + { + "os": "darwin", + "arch": "amd64", + "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/darwin-amd64", + "checksum": "84ed966949e06bebd7d006bc343caf9d736932fd8b37df5cb5b268a28d07bd30", + "archive_path": "darwin-amd64", + "link": true + }, + { + "os": "linux", + "arch": "amd64", + "url": "https://github.com/myitcv/gobin/releases/download/v0.0.10/linux-amd64", + "checksum": "415266d9af98578067051653f5057ea267c51ebf085408df48b118a8b978bac6", + "archive_path": "linux-amd64" + } + ] + } +}