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

Handle different errors and provide nice error messages #162

Merged
merged 6 commits into from
Jul 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions cmd/sourced/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,31 @@ type Command struct {
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Run(os.Args); err != nil {
if workdir.ErrMalformed.Is(err) || dir.ErrNotExist.Is(err) {
fmt.Println(format.Colorize(
format.Red,
`Cannot perform this action, source{d} needs to be initialized first with the 'init' sub command`,
))
}
if err := dir.Prepare(); err != nil {
fmt.Println(err)
log(err)
os.Exit(1)
}

if err := rootCmd.Run(os.Args); err != nil {
log(err)
os.Exit(1)
}
}

func log(err error) {
switch {
case workdir.ErrMalformed.Is(err) || dir.ErrNotExist.Is(err):
printRed("Cannot perform this action, source{d} needs to be initialized first with the 'init' sub command")
case dir.ErrNotValid.Is(err):
printRed("Cannot perform this action, config directory is not valid")
case fmt.Sprintf("%T", err) == "*flags.Error":
// syntax error is already logged by go-cli
default:
// unknown errors have no special message
}
}

func printRed(message string) {
fmt.Println(format.Colorize(format.Red, message))
dpordomingo marked this conversation as resolved.
Show resolved Hide resolved
}
18 changes: 13 additions & 5 deletions cmd/sourced/cmd/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"github.com/pkg/browser"
"github.com/pkg/errors"
"github.com/src-d/sourced-ce/cmd/sourced/compose"
"github.com/src-d/sourced-ce/cmd/sourced/compose/workdir"
"github.com/src-d/sourced-ce/cmd/sourced/dir"
)

// The service name used in docker-compose.yml for the srcd/sourced-ui image
Expand All @@ -34,17 +36,23 @@ func openUI() error {
var stdout bytes.Buffer
// wait for the container to start, it can take a while in some cases
for {
if err := compose.RunWithIO(context.Background(),
os.Stdin, &stdout, nil, "port", containerName, "8088"); err == nil {
err := compose.RunWithIO(context.Background(),
os.Stdin, &stdout, nil, "port", containerName, "8088")

if err == nil {
break
}

if workdir.ErrMalformed.Is(err) || dir.ErrNotExist.Is(err) || dir.ErrNotValid.Is(err) {
return err
}

time.Sleep(1 * time.Second)
}

address := strings.TrimSpace(stdout.String())
if address == "" {
return fmt.Errorf("no address found")
return fmt.Errorf("could not find the public port of %s", containerName)
}

// docker-compose returns 0.0.0.0 which is correct for the bind address
Expand All @@ -61,7 +69,7 @@ func openUI() error {
}

if err := browser.OpenURL(url); err != nil {
errors.Wrap(err, "cannot open browser")
return errors.Wrap(err, "could not open the browser")
}

return nil
Expand All @@ -88,7 +96,7 @@ Once source{d} is fully initialized, the UI will be available, by default at:

select {
case err := <-ch:
return errors.Wrap(err, "an error occurred while opening the UI")
return err
case <-time.After(timeout):
return fmt.Errorf("error opening the UI, the container is not running after %v", timeout)
}
Expand Down
7 changes: 5 additions & 2 deletions cmd/sourced/cmd/workdirs.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ func (c *workdirsCmd) Execute(args []string) error {
return err
}

// ignore errors if active dir doesn't exist or unavailable
active, _ := workdir.Active()
active, err := workdir.Active()
if err != nil {
return err
}

for _, dir := range dirs {
if dir == active {
fmt.Printf("* %s\n", dir)
Expand Down
6 changes: 3 additions & 3 deletions cmd/sourced/compose/workdir/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func ActivePath() (string, error) {

resolvedPath, err := filepath.EvalSymlinks(path)
if os.IsNotExist(err) {
return "", ErrMalformed.New("active", "not found")
return "", ErrMalformed.New("active", err)
}

return resolvedPath, err
Expand Down Expand Up @@ -353,8 +353,8 @@ func workdirsPath() (string, error) {
return filepath.Join(path, "workdirs"), nil
}

// function takes workdirs root and absolute path to workdir
// return human-readable name
// decodeName takes workdirs root and absolute path to workdir
// return human-readable name. It returns an error if the path could not be built
func decodeName(base, target string) (string, error) {
p, err := filepath.Rel(base, target)
if err != nil {
Expand Down
74 changes: 66 additions & 8 deletions cmd/sourced/dir/dir.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Package dir provides functions to manage the $HOME/.sourced and /tmp/srcd
// directories
// Package dir provides functions to manage the config directories.
package dir

import (
Expand All @@ -13,27 +12,86 @@ import (
goerrors "gopkg.in/src-d/go-errors.v1"
)

// ErrNotExist is returned when .sourced dir does not exists
// ErrNotExist is returned when config dir does not exists
var ErrNotExist = goerrors.NewKind("%s does not exist")

// ErrNotValid is returned when config dir is not valid
var ErrNotValid = goerrors.NewKind("%s is not a valid config directory: %s")

// Path returns the absolute path for $SOURCED_DIR, or $HOME/.sourced if unset
// and returns an error if it does not exist or it could not be read.
func Path() (string, error) {
srcdDir, err := path()
if err != nil {
return "", err
}

if err := validate(srcdDir); err != nil {
return "", err
}

return srcdDir, nil
}

func path() (string, error) {
if d := os.Getenv("SOURCED_DIR"); d != "" {
return filepath.Abs(d)
abs, err := filepath.Abs(d)
if err != nil {
return "", errors.Wrap(err, fmt.Sprintf("could not resolve SOURCED_DIR='%s'", d))
}

return abs, nil
}

homedir, err := os.UserHomeDir()
if err != nil {
return "", errors.Wrap(err, "could not detect home directory")
}

srcdDir := filepath.Join(homedir, ".sourced")
_, err = os.Lstat(srcdDir)
return filepath.Join(homedir, ".sourced"), nil
}

// Prepare tries to create the config directory, returning an error if it could not
// be created, or nill if already exist or was successfully created.
func Prepare() error {
srcdDir, err := path()
if err != nil {
return err
}

err = validate(srcdDir)
if ErrNotExist.Is(err) {
if err := os.MkdirAll(srcdDir, os.ModePerm); err != nil {
return ErrNotValid.New(srcdDir, err)
}

return nil
}

return err
}

// validate validates that the passed config dir path is valid
func validate(path string) error {
info, err := os.Stat(path)
if os.IsNotExist(err) {
return "", ErrNotExist.New(srcdDir)
return ErrNotExist.New(path)
}

return srcdDir, nil
if err != nil {
return ErrNotValid.New(path, err)
}

readWriteAccessMode := os.FileMode(0700)
if info.Mode()&readWriteAccessMode != readWriteAccessMode {
return ErrNotValid.New(path, "it has no read-write access")
}

if !info.IsDir() {
return ErrNotValid.New(path, "it is not a directory")
}

return nil
}

// DownloadURL downloads the given url to a file to the
Expand Down