Skip to content

Commit

Permalink
Fix the mimetype check, implement the cool progress checker from go-g…
Browse files Browse the repository at this point in the history
…etter
  • Loading branch information
richardcane committed Sep 22, 2019
1 parent ec66ea6 commit afdddb8
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 59 deletions.
75 changes: 17 additions & 58 deletions cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,16 @@ package cmd
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"

"github.com/dustin/go-humanize"
"github.com/h2non/filetype"
"github.com/gabriel-vasile/mimetype"
getter "github.com/hashicorp/go-getter"
"github.com/spf13/cobra"
"golang.org/x/sys/unix"
)

// WriteCounter tracks the total number of bytes
type WriteCounter struct {
Total uint64
}

func (wc *WriteCounter) Write(p []byte) (int, error) {
n := len(p)
wc.Total += uint64(n)
wc.PrintProgress()
return n, nil
}

// PrintProgress - Helper function to print progress of a download
func (wc WriteCounter) PrintProgress() {
// Clear the line by using a character return to go back to the start and remove
// the remaining characters by filling it with spaces
fmt.Printf("\r%s", strings.Repeat(" ", 50))

// Return again and print current status of download
// We use the humanize package to print the bytes in a meaningful way (e.g. 10 MB)
fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.Total))
}

// installCmd represents the install command
var installCmd = &cobra.Command{
Use: "install",
Expand Down Expand Up @@ -93,23 +67,21 @@ func DownloadKubectl(version string) error {
if err != nil {
log.Fatal(err)
}
kubectl := fmt.Sprintf("%v/.kubemngr/kubectl-%v", homeDir, version)

// Check if current version already exists
if _, err = os.Stat(homeDir + "/.kubemngr/kubectl-" + version); err == nil {
if _, err = os.Stat(kubectl); err == nil {
log.Fatalf("%s is already installed.", version)
}

// Create temp file of kubectl version in tmp directory
out, err := os.Create(homeDir + "/.kubemngr/kubectl-" + version)
out, err := os.Create(kubectl)
if err != nil {
log.Fatal(err)
}

defer out.Close()

// Get OS information to filter download type i.e linux / darwin
uname := getOSInfo()

// Compare system name to set value for building url to download kubectl binary
if uname.Sysname != "Linux" && uname.Sysname != "Darwin" {
log.Fatalf("Unsupported OS: %s\nCheck github.com/zee-ahmed/kubemngr for issues.", uname.Sysname)
Expand All @@ -126,42 +98,29 @@ func DownloadKubectl(version string) error {
machine = strings.ToLower(uname.Machine)
}

// Check to make sure the file is a binary before moving the contents over to the user's home dir
url := "https://storage.googleapis.com/kubernetes-release/release/%v/bin/%v/%v/kubectl"
resp, err := http.Get(fmt.Sprintf(url, version, sys, machine))
if err != nil {
log.Fatal(err)
client := getter.Client{
Src: fmt.Sprintf(url, version, sys, machine),
Dst: kubectl,
ProgressListener: defaultProgressBar,
}
defer resp.Body.Close()

// Initialise WriteCounter and copy the contents of the response body to the tmp file
counter := &WriteCounter{}
_, err = io.Copy(out, io.TeeReader(resp.Body, counter))
fmt.Printf("Downloading %v\n", client.Src)
err = client.Get()
if err != nil {
log.Fatal(err)
}
fmt.Println()

// Check to make sure the file is a binary before moving the contents over to the user's home dir
buf, _ := ioutil.ReadFile(homeDir + "/.kubemngr/kubectl-" + version)

// elf - application/x-executable check
if !filetype.IsArchive(buf) {
fmt.Println("failed to download kubectl file. Are you sure you specified the right version?")
os.Remove(homeDir + "/.kubemngr/kubectl-" + version)
mime, _, err := mimetype.DetectFile(kubectl)
if mime != "application/octet-stream" {
fmt.Printf("The downloaded binary is not in the expected format. Please check the version and try again.")
os.Remove(kubectl)
os.Exit(1)
}

// Set executable permissions on the kubectl binary
if err := os.Chmod(homeDir+"/.kubemngr/kubectl-"+version, 0755); err != nil {
log.Fatal(err)
}

// Rename the tmp file back to the original file and store it in the kubemngr directory
currentFilePath := homeDir + "/.kubemngr/kubectl-" + version
newFilePath := homeDir + "/.kubemngr/kubectl-" + version

err = os.Rename(currentFilePath, newFilePath)
if err != nil {
if err := os.Chmod(kubectl, 0755); err != nil {
log.Fatal(err)
}

Expand Down
80 changes: 80 additions & 0 deletions cmd/progress_tracker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// borrowed from github.com/hashicorp/go-getter/cmd

package cmd

import (
"io"
"path/filepath"
"sync"

"github.com/cheggaaa/pb"
getter "github.com/hashicorp/go-getter"
)

// defaultProgressBar is the default instance of a cheggaaa
// progress bar.
var defaultProgressBar getter.ProgressTracker = &ProgressBar{}

// ProgressBar wraps a github.com/cheggaaa/pb.Pool
// in order to display download progress for one or multiple
// downloads.
//
// If two different instance of ProgressBar try to
// display a progress only one will be displayed.
// It is therefore recommended to use DefaultProgressBar
type ProgressBar struct {
// lock everything below
lock sync.Mutex

pool *pb.Pool

pbs int
}

// ProgressBarConfig .
func ProgressBarConfig(bar *pb.ProgressBar, prefix string) {
bar.SetUnits(pb.U_BYTES)
bar.Prefix(prefix)
}

// TrackProgress instantiates a new progress bar that will
// display the progress of stream until closed.
// total can be 0.
func (cpb *ProgressBar) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
cpb.lock.Lock()
defer cpb.lock.Unlock()

newPb := pb.New64(totalSize)
newPb.Set64(currentSize)
ProgressBarConfig(newPb, filepath.Base(src))
if cpb.pool == nil {
cpb.pool = pb.NewPool()
cpb.pool.Start()
}
cpb.pool.Add(newPb)
reader := newPb.NewProxyReader(stream)

cpb.pbs++
return &readCloser{
Reader: reader,
close: func() error {
cpb.lock.Lock()
defer cpb.lock.Unlock()

newPb.Finish()
cpb.pbs--
if cpb.pbs <= 0 {
cpb.pool.Stop()
cpb.pool = nil
}
return nil
},
}
}

type readCloser struct {
io.Reader
close func() error
}

func (c *readCloser) Close() error { return c.close() }
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ module github.com/zee-ahmed/kubemngr
go 1.12

require (
github.com/cheggaaa/pb v1.0.27
github.com/dustin/go-humanize v1.0.0
github.com/gabriel-vasile/mimetype v0.3.18
github.com/h2non/filetype v1.0.10
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-getter v1.4.0
github.com/hashicorp/go-version v1.2.0
github.com/magiconair/properties v1.8.1 // indirect
github.com/mitchellh/go-homedir v1.1.0
Expand All @@ -14,5 +18,4 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.4.0
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7
golang.org/x/text v0.3.2 // indirect
)
Loading

0 comments on commit afdddb8

Please sign in to comment.