Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Features: add commands to interact with Index Data locally; verify checksum when available #5

Merged
merged 7 commits into from
Jul 4, 2020
52 changes: 2 additions & 50 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ package client
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"

"github.com/mholt/archiver/v3"
"github.com/progrhyme/binq"
"github.com/progrhyme/binq/internal/erron"
"github.com/progrhyme/binq/internal/logs"
"github.com/progrhyme/binq/schema/item"
)

type Mode int
Expand All @@ -35,6 +34,7 @@ type Runner struct {
ServerURL *url.URL
httpClient *http.Client
sourceURL string
sourceItem *item.ItemRevision
tmpdir string
download string
extractDir string
Expand Down Expand Up @@ -103,54 +103,6 @@ func (r *Runner) Run() (err error) {
return nil
}

func (r *Runner) fetch() (err error) {
if r.sourceURL == "" {
return fmt.Errorf("Can't fetch because sourceURL is not set. Source: %s", r.Source)
}
req, err := newHttpGetRequest(r.sourceURL, map[string]string{})
if err != nil {
return err
}
r.Logger.Printf("GET %s", r.sourceURL)
res, _err := r.httpClient.Do(req)
if _err != nil {
return erron.Errorwf(_err, "Failed to execute HTTP request")
}
defer res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("HTTP response is not OK. Code: %d, URL: %s", res.StatusCode, r.Source)
}
r.tmpdir, _err = ioutil.TempDir(os.TempDir(), "binq.*")
if _err != nil {
return erron.Errorwf(_err, "Failed to create tempdir")
}
defer func() {
if _err != nil {
os.RemoveAll(r.tmpdir)
}
}()

url, _err := url.Parse(r.sourceURL)
if _err != nil {
// Unexpected case
return erron.Errorwf(_err, "Failed to parse source URL: %v", r.sourceURL)
}
base := path.Base(url.Path)
r.download = filepath.Join(r.tmpdir, base)
dl, _err := os.Create(r.download)
if _err != nil {
return erron.Errorwf(_err, "Failed to open file: %s", r.download)
}
defer dl.Close()
_, _err = io.Copy(dl, res.Body)
if _err != nil {
return erron.Errorwf(_err, "Failed to read HTTP response")
}
r.Logger.Debugf("Saved file %s", r.download)

return nil
}

func (r *Runner) extract() (err error) {
r.extracted = false
uai, _err := archiver.ByExtension(r.download)
Expand Down
91 changes: 91 additions & 0 deletions client/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package client

import (
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"path"
"path/filepath"

"github.com/progrhyme/binq/internal/erron"
"github.com/progrhyme/binq/schema/item"
)

func (r *Runner) fetch() (err error) {
if r.sourceURL == "" {
return fmt.Errorf("Can't fetch because sourceURL is not set. Source: %s", r.Source)
}
req, err := newHttpGetRequest(r.sourceURL, map[string]string{})
if err != nil {
return err
}
r.Logger.Printf("GET %s", r.sourceURL)
res, _err := r.httpClient.Do(req)
if _err != nil {
return erron.Errorwf(_err, "Failed to execute HTTP request")
}
defer res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("HTTP response is not OK. Code: %d, URL: %s", res.StatusCode, r.Source)
}
r.tmpdir, _err = ioutil.TempDir(os.TempDir(), "binq.*")
if _err != nil {
return erron.Errorwf(_err, "Failed to create tempdir")
}
defer func() {
if _err != nil {
os.RemoveAll(r.tmpdir)
}
}()

url, _err := url.Parse(r.sourceURL)
if _err != nil {
// Unexpected case
return erron.Errorwf(_err, "Failed to parse source URL: %v", r.sourceURL)
}
base := path.Base(url.Path)
r.download = filepath.Join(r.tmpdir, base)
dl, _err := os.Create(r.download)
if _err != nil {
return erron.Errorwf(_err, "Failed to open file: %s", r.download)
}
defer dl.Close()

if r.sourceItem != nil {
if cs := r.sourceItem.GetChecksum(base); cs != nil {
return r.downloadWithChecksum(cs, res.Body, dl)
}
r.Logger.Noticef("Checksum is not provided. Skip verification")
}

// Download without checksum
_, _err = io.Copy(dl, res.Body)
if _err != nil {
return erron.Errorwf(_err, "Failed to read HTTP response")
}
r.Logger.Debugf("Saved file %s", r.download)

return nil
}

func (r *Runner) downloadWithChecksum(cs *item.ItemChecksum, content io.ReadCloser, destFile *os.File) (err error) {
sum, hasher := cs.GetSumAndHasher()
tee := io.TeeReader(content, hasher)
_, _err := io.Copy(destFile, tee)
if _err != nil {
return erron.Errorwf(_err, "Failed to read HTTP response")
}
r.Logger.Debugf("Saved file %s", r.download)

cksum := hex.EncodeToString(hasher.Sum(nil))
r.Logger.Debugf("Sum: %s", cksum)
if sum != cksum {
r.Logger.Errorf("Checksum differs! Maybe corrupt? Want: %s, Got: %s", sum, cksum)
} else {
r.Logger.Infof("Checksum is OK")
}
return nil
}
6 changes: 4 additions & 2 deletions client/prefetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ func (r *Runner) prefetch() (err error) {
return _err
}

srcURL, err := tgt.GetLatestURL(item.ItemURLParam{OS: runtime.GOOS, Arch: runtime.GOARCH})
rev := tgt.GetLatest()
srcURL, err := rev.GetURL(item.ItemURLParam{OS: runtime.GOOS, Arch: runtime.GOARCH})
if err != nil {
return err
}
Expand All @@ -52,8 +53,9 @@ func (r *Runner) prefetch() (err error) {
}

r.sourceURL = srcURL
r.sourceItem = rev

return err
return nil
}

func (r *Runner) prefetchItemByURL(hc *http.Client, urlPath string) (tgt *item.Item, err error) {
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
37 changes: 18 additions & 19 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package cli

import (
"errors"
"fmt"
"io"
"path/filepath"

"github.com/progrhyme/binq"
"github.com/progrhyme/binq/internal/logs"
"github.com/spf13/pflag"
)

const (
exitOK = iota
exitNG
)

var (
errFileNotFound = errors.New("File not found")
errCanceled = errors.New("Canceled")
)

type CLI struct {
OutStream, ErrStream io.Writer
}
Expand All @@ -23,16 +28,6 @@ func NewCLI(outs, errs io.Writer) *CLI {
return &CLI{OutStream: outs, ErrStream: errs}
}

type commonCmd struct {
outs, errs io.Writer
prog, name string
flags *pflag.FlagSet
}

type commonOpts struct {
help, verbose, debug *bool
}

func (c *CLI) Run(args []string) (exit int) {
prog := filepath.Base(args[0])

Expand All @@ -56,18 +51,22 @@ func (c *CLI) Run(args []string) (exit int) {
revisor := newReviseCmd(common)
revisor.name = "revise"
return revisor.run(args[2:])
case "register":
registrar := newRegisterCmd(common)
registrar.name = "register"
return registrar.run(args[2:])
case "modify":
modifier := newModifyCmd(common)
modifier.name = "modify"
return modifier.run(args[2:])
case "deregister":
deregistrar := newDeregisterCmd(common)
deregistrar.name = "deregister"
return deregistrar.run(args[2:])
case "version":
fmt.Fprintf(c.OutStream, "Version: %s\n", binq.Version)
return exitOK
}

return installer.run(args[1:])
}

func newCommonOpts(fs *pflag.FlagSet) *commonOpts {
return &commonOpts{
help: fs.BoolP("help", "h", false, "# Show help"),
verbose: fs.BoolP("verbose", "v", false, "# Show verbose messages"),
debug: fs.Bool("debug", false, "# Show debug messages"),
}
}
38 changes: 38 additions & 0 deletions internal/cli/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cli

import (
"io"

"github.com/spf13/pflag"
)

type runner interface {
getOuts() io.Writer
getErrs() io.Writer
}

type commonCmd struct {
outs, errs io.Writer
prog, name string
flags *pflag.FlagSet
}

type commonOpts struct {
help, verbose, debug *bool
}

func (cmd *commonCmd) getOuts() io.Writer {
return cmd.outs
}

func (cmd *commonCmd) getErrs() io.Writer {
return cmd.errs
}

func newCommonOpts(fs *pflag.FlagSet) *commonOpts {
return &commonOpts{
help: fs.BoolP("help", "h", false, "# Show help"),
verbose: fs.BoolP("verbose", "v", false, "# Show verbose messages"),
debug: fs.Bool("debug", false, "# Show debug messages"),
}
}
Loading