Skip to content

Commit

Permalink
Adding lenter and travis CI support (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
wagoodman committed Feb 3, 2018
1 parent 1012bef commit da02808
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 236 deletions.
2 changes: 1 addition & 1 deletion .goreleaser.yml
Expand Up @@ -8,7 +8,7 @@ builds:
- linux
goarch:
- amd64
ldflags: -s -w -X main.Version={{.Version}} -X main.GitCommit={{.Commit}} -X main.BuildTime={{.Date}}`.
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.buildTime={{.Date}}`.

archive:
format: tar.gz
Expand Down
21 changes: 6 additions & 15 deletions .travis.yml
Expand Up @@ -17,25 +17,16 @@ matrix:
# tests pass on the stable versions of Go.
fast_finish: true

# Don't email me the results of the test runs.
notifications:
email: false

# Anything in before_script that returns a nonzero exit code will
# flunk the build and immediately stop. It's sorta like having
# set -e enabled in bash.
before_script:
- GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/) # All the .go files, excluding vendor/
- go get github.com/golang/lint/golint # Linter
- go get honnef.co/go/tools/cmd/megacheck # Badass static analyzer/linter
- go get -t ./...
- go get github.com/golang/lint/golint
- go get honnef.co/go/tools/cmd/megacheck
- go get github.com/fzipp/gocyclo

# script always run to completion (set +e). All of these code checks are must haves
# in a modern Go project.
# Note: scripts always run to completion
script:
- test -z $(gofmt -s -l $GO_FILES) # Fail if a .go file hasn't been formatted with gofmt
- go test -v -race ./... # Run all the tests with the race detector enabled
- go vet ./... # go vet is the official Go static analyzer
- megacheck ./... # "go vet on steroids" + linter
- gocyclo -over 19 $GO_FILES # forbid code with huge functions
- golint -set_exit_status $(go list ./...) # one last linter
- make test
- make validate
27 changes: 14 additions & 13 deletions config.go
Expand Up @@ -42,6 +42,7 @@ var config struct {
commandTimeCache map[string]time.Duration
}

// CliOptions is the exhaustive set of all command line options available on bashful
type CliOptions struct {
RunTags []string
RunTagSet mapset.Set
Expand Down Expand Up @@ -171,11 +172,11 @@ type TaskConfig struct {
StopOnFailure bool `yaml:"stop-on-failure"`

// Tags is a list of strings that is used to filter down which task are run at runtime
Tags StringArray `yaml:"tags"`
Tags stringArray `yaml:"tags"`
TagSet mapset.Set

// Url is the http/https link to a bash/executable resource
Url string `yaml:"url"`
// URL is the http/https link to a bash/executable resource
URL string `yaml:"url"`
}

// NewTaskConfig creates a new TaskConfig populated with sane default values (derived from the global OptionsConfig)
Expand All @@ -201,10 +202,10 @@ func (taskConfig *TaskConfig) UnmarshalYAML(unmarshal func(interface{}) error) e
return nil
}

type StringArray []string
type stringArray []string

// allow passing a single value or multiple values into a yaml string (e.g. `tags: thing` or `{tags: [thing1, thing2]}`)
func (a *StringArray) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (a *stringArray) UnmarshalYAML(unmarshal func(interface{}) error) error {
var multi []string
err := unmarshal(&multi)
if err != nil {
Expand Down Expand Up @@ -252,7 +253,7 @@ func removeOneValue(slice []float64, value float64) []float64 {
func readTimeCache() {
if config.CachePath == "" {
cwd, err := os.Getwd()
CheckError(err, "Unable to get CWD.")
checkError(err, "Unable to get CWD.")
config.CachePath = path.Join(cwd, ".bashful")
}

Expand All @@ -274,7 +275,7 @@ func readTimeCache() {
config.commandTimeCache = make(map[string]time.Duration)
if doesFileExist(config.etaCachePath) {
err := Load(config.etaCachePath, &config.commandTimeCache)
CheckError(err, "Unable to load command eta cache.")
checkError(err, "Unable to load command eta cache.")
}
}

Expand All @@ -296,9 +297,9 @@ func (taskConfig *TaskConfig) inflate() (tasks []TaskConfig) {
}
newConfig.Name = strings.Replace(newConfig.Name, config.Options.ReplicaReplaceString, replicaValue, -1)
newConfig.CmdString = strings.Replace(newConfig.CmdString, config.Options.ReplicaReplaceString, replicaValue, -1)
newConfig.Url = strings.Replace(newConfig.Url, config.Options.ReplicaReplaceString, replicaValue, -1)
newConfig.URL = strings.Replace(newConfig.URL, config.Options.ReplicaReplaceString, replicaValue, -1)

newConfig.Tags = make(StringArray, len(taskConfig.Tags))
newConfig.Tags = make(stringArray, len(taskConfig.Tags))
for k := range taskConfig.Tags {
newConfig.Tags[k] = strings.Replace(taskConfig.Tags[k], config.Options.ReplicaReplaceString, replicaValue, -1)
}
Expand All @@ -316,7 +317,7 @@ func parseRunYaml(yamlString []byte) {
config.Options = NewOptionsConfig()

err := yaml.Unmarshal(yamlString, &config)
CheckError(err, "Error: Unable to parse given yaml")
checkError(err, "Error: Unable to parse given yaml")

config.Options.validate()

Expand Down Expand Up @@ -411,7 +412,7 @@ func (options *OptionsConfig) validate() {
}

func (taskConfig *TaskConfig) validate() {
if taskConfig.CmdString == "" && len(taskConfig.ParallelTasks) == 0 && taskConfig.Url == "" {
if taskConfig.CmdString == "" && len(taskConfig.ParallelTasks) == 0 && taskConfig.URL == "" {
exitWithErrorMessage("Task '" + taskConfig.Name + "' misconfigured (A configured task must have at least 'cmd', 'url', or 'parallel-tasks' configured)")
}
}
Expand All @@ -425,7 +426,7 @@ func CreateTasks() (finalTasks []*Task) {

// finalize task by appending to the set of final tasks
task := NewTask(taskConfig, nextDisplayIdx, "")
finalTasks = append(finalTasks, &task)
finalTasks = append(finalTasks, task)
}

// now that all tasks have been inflated, set the total eta
Expand All @@ -447,7 +448,7 @@ func ReadConfig(userYamlPath string) {
readTimeCache()

yamlString, err := ioutil.ReadFile(userYamlPath)
CheckError(err, "Unable to read yaml config.")
checkError(err, "Unable to read yaml config.")

parseRunYaml(yamlString)

Expand Down
80 changes: 39 additions & 41 deletions download.go
Expand Up @@ -25,7 +25,7 @@ var registry struct {

func getFilename(urlStr string) string {
uri, err := url.Parse(urlStr)
CheckError(err, "Unable to parse URI")
checkError(err, "Unable to parse URI")

pathElements := strings.Split(uri.Path, "/")

Expand All @@ -44,30 +44,27 @@ func monitorDownload(requests map[*grab.Request][]*Task, response *grab.Response
if response.IsComplete() {
if response.HTTPResponse.StatusCode > 399 || response.HTTPResponse.StatusCode < 200 {
return red("Failed!")
} else {
return fmt.Sprintf("%7s [%v]",
"100.00%",
humanize.Bytes(uint64(size)))
}
} else {

progressValue := 100 * response.Progress()
var progress string
if progressValue > 100 || progressValue < 0 {
progress = "???%"
} else {
progress = fmt.Sprintf("%.2f%%", progressValue)
}

return fmt.Sprintf("%7s [%v / %v]",
progress,
humanize.Bytes(uint64(response.BytesComplete())),
return fmt.Sprintf("%7s [%v]",
"100.00%",
humanize.Bytes(uint64(size)))
}

progressValue := 100 * response.Progress()
var progress string
if progressValue > 100 || progressValue < 0 {
progress = "???%"
} else {
progress = fmt.Sprintf("%.2f%%", progressValue)
}

return fmt.Sprintf("%7s [%v / %v]",
progress,
humanize.Bytes(uint64(response.BytesComplete())),
humanize.Bytes(uint64(size)))
})
bar.PrependFunc(func(b *uiprogress.Bar) string {
urlStr := requests[response.Request][0].Config.Url
urlStr := requests[response.Request][0].Config.URL
if len(urlStr) > 25 {
urlStr = getFilename(urlStr)
}
Expand All @@ -94,28 +91,29 @@ Loop:
expectedFilepath := registry.urltoFilename[response.Request.URL().String()]
if response.Filename != expectedFilepath {
err := os.Rename(response.Filename, expectedFilepath)
CheckError(err, "Unable to rename downloaded asset: "+response.Filename)
checkError(err, "Unable to rename downloaded asset: "+response.Filename)
}

// ensure the asset is executable
err := os.Chmod(expectedFilepath, 0755)
CheckError(err, "Unable to make asset executable: "+expectedFilepath)
checkError(err, "Unable to make asset executable: "+expectedFilepath)

// update all tasks using this asset to use the final filepath
for _, task := range registry.requestToTask[response.Request] {
task.UpdateExec(expectedFilepath)
task.updateExec(expectedFilepath)
}

waiter.Done()

}

// AddRequest extracts all URLS configured for a given task (does not examine child tasks) and queues them for download
func AddRequest(task *Task) {
if task.Config.Url != "" {
request, ok := registry.urlToRequest[task.Config.Url]
if task.Config.URL != "" {
request, ok := registry.urlToRequest[task.Config.URL]
if !ok {
// never seen this url before
filepath := path.Join(config.downloadCachePath, getFilename(task.Config.Url))
filepath := path.Join(config.downloadCachePath, getFilename(task.Config.URL))

if _, err := os.Stat(filepath); err == nil {
// the asset already exists, skip (unless it has an unexpected checksum)
Expand All @@ -127,35 +125,35 @@ func AddRequest(task *Task) {
}

}
task.UpdateExec(filepath)
task.updateExec(filepath)
return
} else {
// the asset has not already been downloaded
request, _ = grab.NewRequest(filepath, task.Config.Url)
}
// the asset has not already been downloaded
request, _ = grab.NewRequest(filepath, task.Config.URL)

// workaround for https://github.com/cavaliercoder/grab/issues/25, allow the ability to follow 302s
//request.IgnoreBadStatusCodes = true
// workaround for https://github.com/cavaliercoder/grab/issues/25, allow the ability to follow 302s
//request.IgnoreBadStatusCodes = true

registry.urltoFilename[task.Config.Url] = filepath
registry.urlToRequest[task.Config.Url] = request
}
registry.urltoFilename[task.Config.URL] = filepath
registry.urlToRequest[task.Config.URL] = request
}
registry.requestToTask[request] = append(registry.requestToTask[request], task)
}
}

func md5OfFile(filepath string) string {
f, err := os.Open(filepath)
CheckError(err, "File does not exist: "+filepath)
checkError(err, "File does not exist: "+filepath)
defer f.Close()

h := md5.New()
_, err = io.Copy(h, f)
CheckError(err, "Could not calculate md5 checksum of "+filepath)
checkError(err, "Could not calculate md5 checksum of "+filepath)

return fmt.Sprintf("%x", h.Sum(nil))
}

// DownloadAssets fetches all assets for the given task
func DownloadAssets(tasks []*Task) {
registry.urlToRequest = make(map[string]*grab.Request)
registry.requestToTask = make(map[*grab.Request][]*Task)
Expand Down Expand Up @@ -189,12 +187,12 @@ func DownloadAssets(tasks []*Task) {
}

if len(allRequests) == 0 {
logToMain("No assets to download", MAJOR_FORMAT)
logToMain("No assets to download", majorFormat)
return
}

fmt.Println(bold("Downloading referenced assets"))
logToMain("Downloading referenced assets", MAJOR_FORMAT)
logToMain("Downloading referenced assets", majorFormat)

uiprogress.Empty = ' '
uiprogress.Fill = '|'
Expand All @@ -220,11 +218,11 @@ func DownloadAssets(tasks []*Task) {
foundFailedAsset := false
for _, response := range responses {
if err := response.Err(); err != nil {
logToMain(fmt.Sprintf(red("Failed to download '%s': %s"), response.Request.URL(), err.Error()), ERROR_FORMAT)
logToMain(fmt.Sprintf(red("Failed to download '%s': %s"), response.Request.URL(), err.Error()), errorFormat)
foundFailedAsset = true
}
if response.HTTPResponse.StatusCode > 399 || response.HTTPResponse.StatusCode < 200 {
logToMain(fmt.Sprintf(red("Failed to download '%s': Bad HTTP response code (%d)"), response.Request.URL(), response.HTTPResponse.StatusCode), ERROR_FORMAT)
logToMain(fmt.Sprintf(red("Failed to download '%s': Bad HTTP response code (%d)"), response.Request.URL(), response.HTTPResponse.StatusCode), errorFormat)
foundFailedAsset = true
}
}
Expand All @@ -246,5 +244,5 @@ func DownloadAssets(tasks []*Task) {
exitWithErrorMessage("Asset download failed")
}

logToMain("Asset download complete", MAJOR_FORMAT)
logToMain("Asset download complete", majorFormat)
}
27 changes: 15 additions & 12 deletions log.go
Expand Up @@ -11,15 +11,17 @@ import (
)

var (
mainLogChan chan LogItem = make(chan LogItem)
mainLogConcatChan chan LogConcat = make(chan LogConcat)
mainLogChan = make(chan LogItem)
mainLogConcatChan = make(chan LogConcat)
)

// LogItem represents all fields in a log message
type LogItem struct {
Name string
Message string
}

// LogConcat contains all metadata necessary to concatenate a subprocess log to the main log
type LogConcat struct {
File string
}
Expand Down Expand Up @@ -65,10 +67,11 @@ func setupLogging() {
}

removeDirContents(config.logCachePath)
go MainLogger(config.Options.LogPath)
go mainLogger(config.Options.LogPath)
}

func SingleLogger(SingleLogChan chan LogItem, name, logPath string) {
// singleLogger creats a separatly managed log (typically for an individual task to be later concatenated with the mainlog)
func singleLogger(SingleLogChan chan LogItem, name, logPath string) {

file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
Expand All @@ -84,22 +87,22 @@ func SingleLogger(SingleLogChan chan LogItem, name, logPath string) {
logger.SetFlags(0)

for {
select {
case logObj, ok := <-SingleLogChan:
if ok {
logger.Print(logObj.Message)
} else {
SingleLogChan = nil
}
logObj, ok := <-SingleLogChan
if ok {
logger.Print(logObj.Message)
} else {
SingleLogChan = nil
}

if SingleLogChan == nil {
break
}
}

}

func MainLogger(logPath string) {
// mainLogger creates the main log configured by the `log-path` option
func mainLogger(logPath string) {

file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
Expand Down

0 comments on commit da02808

Please sign in to comment.