diff --git a/cli/cli.go b/cli/cli.go index ee7c760..9cbf12f 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -2,6 +2,7 @@ package cli import ( + "bytes" "context" "fmt" "io" @@ -9,9 +10,9 @@ import ( "strings" "sync" + "github.com/TouchBistro/goutils/log" "github.com/TouchBistro/goutils/progress" "github.com/TouchBistro/tb/engine" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -58,22 +59,10 @@ type Container struct { Logger *Logger } -// ExitError is used to signal that the CLI should exit with a given code and message. -type ExitError struct { - Code int - Message string - Err error -} - -func (e *ExitError) Error() string { - return e.Message -} - -// Logger that wraps a logrus.Logger and implements progress.OutputLogger. -// It writes to both stderr and a temp file. +// Logger is a logger that writes to both stderr and a temp file. type Logger struct { - // Embed a logrus logger to automatically implement all the log methods. - *logrus.Logger + // Embed a logger to automatically implement all the log methods. + *log.Logger f *os.File // temp file where all logs are written h *loggerHook // hook for also logging to stderr @@ -94,30 +83,23 @@ func NewLogger(verbose bool) (*Logger, error) { return nil, fmt.Errorf("failed to create log file: %w", err) } - logger := &logrus.Logger{ - Out: f, - Formatter: &logrus.TextFormatter{ - DisableColors: true, - }, - Hooks: make(logrus.LevelHooks), - Level: logrus.DebugLevel, - } + logger := log.New( + log.WithOutput(f), + log.WithFormatter(&log.TextFormatter{}), + log.WithLevel(log.LevelDebug), + ) h := &loggerHook{ w: os.Stderr, verbose: verbose, - formatter: &logrus.TextFormatter{ + formatter: &log.TextFormatter{ + Pretty: true, DisableTimestamp: true, - ForceColors: true, }, } logger.AddHook(h) return &Logger{logger, f, h}, nil } -func (l *Logger) WithFields(fields progress.Fields) progress.Logger { - return progressLogger{l.Logger.WithFields(logrus.Fields(fields))} -} - func (l *Logger) Output() io.Writer { // Use the hook's output, not the actual logger's // since that is where stderr logs go. @@ -159,40 +141,28 @@ func (l *Logger) Cleanup(remove bool) error { return nil } -// progressLogger is a simple wrapper for a logrus.FieldLogger that makes -// it implement progress.Logger. -type progressLogger struct { - logrus.FieldLogger -} - -func (pl progressLogger) WithFields(fields progress.Fields) progress.Logger { - return progressLogger{pl.FieldLogger.WithFields(logrus.Fields(fields))} -} - -// loggerHook is a logrus hook to writes to an io.Writer. +// loggerHook is a logger hook to writes to an io.Writer. type loggerHook struct { w io.Writer verbose bool mu sync.Mutex - formatter logrus.Formatter + formatter log.Formatter + buf bytes.Buffer } -func (h *loggerHook) Levels() []logrus.Level { - // We want the hook to fire on all levels and then we will decide what to do. - return logrus.AllLevels -} - -func (h *loggerHook) Fire(e *logrus.Entry) error { - if e.Level == logrus.DebugLevel && !h.verbose { +func (h *loggerHook) Run(e *log.Entry) error { + if e.Level == log.LevelDebug && !h.verbose { // Ignore debug level if we aren't verbose return nil } - b, err := h.formatter.Format(e) + + h.mu.Lock() + defer h.mu.Unlock() + h.buf.Reset() + b, err := h.formatter.Format(e, &h.buf) if err != nil { return err } - h.mu.Lock() - defer h.mu.Unlock() _, err = h.w.Write(b) return err } diff --git a/cli/commands/app/desktop/run.go b/cli/commands/app/desktop/run.go index fcb6c59..b0dc0b4 100644 --- a/cli/commands/app/desktop/run.go +++ b/cli/commands/app/desktop/run.go @@ -1,6 +1,7 @@ package desktop import ( + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/engine" "github.com/spf13/cobra" @@ -33,9 +34,9 @@ Run the build for a specific branch: Branch: opts.branch, }) if err != nil { - return &cli.ExitError{ - Message: "Failed to run desktop app", - Err: err, + return &fatal.Error{ + Msg: "Failed to run desktop app", + Err: err, } } c.Tracker.Info("✔ Launched desktop app") diff --git a/cli/commands/app/ios/logs.go b/cli/commands/app/ios/logs.go index 56e597e..be35c09 100644 --- a/cli/commands/app/ios/logs.go +++ b/cli/commands/app/ios/logs.go @@ -1,9 +1,11 @@ package ios import ( + "errors" "os" "os/exec" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/engine" "github.com/spf13/cobra" @@ -45,11 +47,11 @@ Displays the last 20 logs in an iOS 12.4 iPad Air 2 simulator: tail.Stdout = os.Stdout tail.Stderr = os.Stderr if err := tail.Run(); err != nil { - code := tail.ProcessState.ExitCode() - if code == -1 { - code = 1 + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + return &fatal.Error{Code: exitErr.ExitCode()} } - os.Exit(code) + return &fatal.Error{Err: err} } return nil }, diff --git a/cli/commands/app/ios/run.go b/cli/commands/app/ios/run.go index 9720d08..1c539aa 100644 --- a/cli/commands/app/ios/run.go +++ b/cli/commands/app/ios/run.go @@ -1,6 +1,7 @@ package ios import ( + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/engine" "github.com/spf13/cobra" @@ -39,9 +40,9 @@ Run the build for specific branch in an iOS 12.3 iPad Air 2 simulator: Branch: opts.branch, }) if err != nil { - return &cli.ExitError{ - Message: "Failed to run iOS app", - Err: err, + return &fatal.Error{ + Msg: "Failed to run iOS app", + Err: err, } } c.Tracker.Info("✔ Launched iOS app") diff --git a/cli/commands/clone.go b/cli/commands/clone.go index 2123764..2ca0a78 100644 --- a/cli/commands/clone.go +++ b/cli/commands/clone.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/TouchBistro/goutils/errors" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/integrations/git" "github.com/TouchBistro/tb/resource" @@ -25,16 +26,16 @@ func newCloneCommand(c *cli.Container) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { s, err := c.Engine.ResolveService(args[0]) if errors.Is(err, resource.ErrNotFound) { - return &cli.ExitError{ - Message: "Try running `tb list` to see available services", - Err: err, + return &fatal.Error{ + Msg: "Try running `tb list` to see available services", + Err: err, } } else if err != nil { return err } if !s.HasGitRepo() { - return &cli.ExitError{ - Message: fmt.Sprintf("%s does not have a repo", s.FullName()), + return &fatal.Error{ + Msg: fmt.Sprintf("%s does not have a repo", s.FullName()), } } diff --git a/cli/commands/db.go b/cli/commands/db.go index 47a572b..73c3e20 100644 --- a/cli/commands/db.go +++ b/cli/commands/db.go @@ -9,6 +9,7 @@ import ( "github.com/TouchBistro/goutils/command" "github.com/TouchBistro/goutils/errors" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/engine" "github.com/spf13/cobra" @@ -31,9 +32,9 @@ func newDBCommand(c *cli.Container) *cobra.Command { serviceName := args[0] dbConf, err := getDbConf(c.Ctx, c, serviceName) if err != nil { - return &cli.ExitError{ - Message: "Could not retrieve database config for this service.", - Err: err, + return &fatal.Error{ + Msg: "Could not retrieve database config for this service.", + Err: err, } } @@ -52,14 +53,14 @@ func newDBCommand(c *cli.Container) *cobra.Command { connArg = fmt.Sprintf("-U %s -P %s -S localhost -d %s", dbConf.user, dbConf.password, dbConf.name) fmt.Println(connArg) default: - return &cli.ExitError{ - Message: fmt.Sprintf("DB_TYPE %s is not currently supported by tb db. Please consider making a pull request or let the maintainers know about your use case.", dbConf.dbType), + return &fatal.Error{ + Msg: fmt.Sprintf("DB_TYPE %s is not currently supported by tb db. Please consider making a pull request or let the maintainers know about your use case.", dbConf.dbType), } } - if !command.IsAvailable(cliName) { - return &cli.ExitError{ - Message: fmt.Sprintf("This command requires %s for %s, which uses a %s database.\n Please install it then run tb db.", cliName, serviceName, dbConf.dbType), + if !command.Exists(cliName) { + return &fatal.Error{ + Msg: fmt.Sprintf("This command requires %s for %s, which uses a %s database.\n Please install it then run tb db.", cliName, serviceName, dbConf.dbType), } } @@ -68,9 +69,9 @@ func newDBCommand(c *cli.Container) *cobra.Command { err = command.New(command.WithStdin(os.Stdin), command.WithStdout(os.Stdout), command.WithStderr(os.Stderr)). Exec(cliName, strings.Fields(connArg)...) if err != nil { - return &cli.ExitError{ - Message: fmt.Sprintf("could not start database client %s", cliName), - Err: err, + return &fatal.Error{ + Msg: fmt.Sprintf("could not start database client %s", cliName), + Err: err, } } return nil diff --git a/cli/commands/down.go b/cli/commands/down.go index 1f538e1..d7686ee 100644 --- a/cli/commands/down.go +++ b/cli/commands/down.go @@ -1,6 +1,7 @@ package commands import ( + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/engine" "github.com/spf13/cobra" @@ -27,9 +28,9 @@ Stop and remove on the postgres and redis containers: RunE: func(cmd *cobra.Command, args []string) error { err := c.Engine.Down(c.Ctx, engine.DownOptions{ServiceNames: args}) if err != nil { - return &cli.ExitError{ - Message: "Failed to stop services", - Err: err, + return &fatal.Error{ + Msg: "Failed to stop services", + Err: err, } } c.Tracker.Info("✔ Stopped services") diff --git a/cli/commands/exec.go b/cli/commands/exec.go index a5150ad..d950b32 100644 --- a/cli/commands/exec.go +++ b/cli/commands/exec.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/engine" "github.com/spf13/cobra" @@ -46,11 +47,10 @@ Start an interactive bash shell in the core-database container: if err != nil { return err } - if exitCode == -1 { - exitCode = 1 + if exitCode != 0 { + // Match the exit code of the command + return &fatal.Error{Code: exitCode} } - // Match the exit code of the command - os.Exit(exitCode) return nil }, } diff --git a/cli/commands/images.go b/cli/commands/images.go index 706f159..9ebe191 100644 --- a/cli/commands/images.go +++ b/cli/commands/images.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/TouchBistro/goutils/errors" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" dockerregistry "github.com/TouchBistro/tb/integrations/docker/registry" "github.com/TouchBistro/tb/resource" @@ -32,16 +33,16 @@ func newImagesCommand(c *cli.Container) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { service, err := c.Engine.ResolveService(args[0]) if errors.Is(err, resource.ErrNotFound) { - return &cli.ExitError{ - Message: "Try running `tb list` to see available services", - Err: err, + return &fatal.Error{ + Msg: "Try running `tb list` to see available services", + Err: err, } } else if err != nil { return err } if service.Remote.Image == "" { - return &cli.ExitError{ - Message: fmt.Sprintf("%s is not available from a remote docker registry", service.FullName()), + return &fatal.Error{ + Msg: fmt.Sprintf("%s is not available from a remote docker registry", service.FullName()), } } @@ -54,9 +55,9 @@ func newImagesCommand(c *cli.Container) *cobra.Command { imgs, err := dockerRegistry.FetchRepoImages(cmd.Context(), service.Remote.Image, opts.max) c.Tracker.Stop() if err != nil { - return &cli.ExitError{ - Message: "Failed to fetch docker images", - Err: err, + return &fatal.Error{ + Msg: "Failed to fetch docker images", + Err: err, } } for _, img := range imgs { diff --git a/cli/commands/nuke.go b/cli/commands/nuke.go index 52e09c5..99bea9d 100644 --- a/cli/commands/nuke.go +++ b/cli/commands/nuke.go @@ -3,6 +3,7 @@ package commands import ( "os" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/engine" "github.com/spf13/cobra" @@ -54,8 +55,8 @@ Remove everything (completely wipe all tb data): if !opts.nukeContainers && !opts.nukeImages && !opts.nukeVolumes && !opts.nukeNetworks && !opts.nukeRepos && !opts.nukeDesktopApps && !opts.nukeIOSBuilds && !opts.nukeRegistries && !opts.nukeAll { - return &cli.ExitError{ - Message: "Error: Must specify what to nuke. Try tb nuke --help to see all the options.", + return &fatal.Error{ + Msg: "Error: Must specify what to nuke. Try tb nuke --help to see all the options.", } } err := c.Engine.Nuke(c.Ctx, engine.NukeOptions{ @@ -69,18 +70,18 @@ Remove everything (completely wipe all tb data): RemoveRegistries: opts.nukeRegistries || opts.nukeAll, }) if err != nil { - return &cli.ExitError{ - Message: "Failed to clean up tb data", - Err: err, + return &fatal.Error{ + Msg: "Failed to clean up tb data", + Err: err, } } // If --all was used removed the entire .tb dir as a way to completely clean up all trace of tb. if opts.nukeAll { if err := os.RemoveAll(c.Engine.Workdir()); err != nil { - return &cli.ExitError{ - Message: "Failed to remove .tb root directory", - Err: err, + return &fatal.Error{ + Msg: "Failed to remove .tb root directory", + Err: err, } } } diff --git a/cli/commands/registry/add.go b/cli/commands/registry/add.go index d773d6a..f111b8c 100644 --- a/cli/commands/registry/add.go +++ b/cli/commands/registry/add.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/TouchBistro/goutils/color" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/config" "github.com/spf13/cobra" @@ -29,9 +30,9 @@ Add the registry named TouchBistro/tb-registry-example: c.Tracker.Infof(color.Green("☑ registry %s has already been added"), registryName) return nil } else if err != nil { - return &cli.ExitError{ - Message: fmt.Sprintf("failed to add registry %s", registryName), - Err: err, + return &fatal.Error{ + Msg: fmt.Sprintf("failed to add registry %s", registryName), + Err: err, } } c.Tracker.Infof(color.Green("Successfully added registry %s"), registryName) diff --git a/cli/commands/registry/validate.go b/cli/commands/registry/validate.go index 8dc3f35..9786cda 100644 --- a/cli/commands/registry/validate.go +++ b/cli/commands/registry/validate.go @@ -5,6 +5,7 @@ import ( "io/fs" "github.com/TouchBistro/goutils/color" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/registry" "github.com/spf13/cobra" @@ -29,7 +30,7 @@ Validate the config files in the current directory: tb registry validate .`, RunE: func(cmd *cobra.Command, args []string) error { registryPath := args[0] - c.Tracker.Infof(color.Cyan("Validating registry files at path %s..."), registryPath) + c.Tracker.Infof(color.Cyan("Validating registry files at path %q"), registryPath) valid := true result := registry.Validate(registryPath, registry.ValidateOptions{ @@ -64,8 +65,8 @@ Validate the config files in the current directory: } if !valid { - return &cli.ExitError{ - Message: color.Red("❌ registry is invalid"), + return &fatal.Error{ + Msg: color.Red("❌ registry is invalid"), } } c.Tracker.Info(color.Green("✅ registry is valid")) diff --git a/cli/commands/root.go b/cli/commands/root.go index d6202cd..8976e4a 100644 --- a/cli/commands/root.go +++ b/cli/commands/root.go @@ -7,7 +7,9 @@ import ( "os" "github.com/TouchBistro/goutils/color" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/goutils/progress" + "github.com/TouchBistro/goutils/spinner" "github.com/TouchBistro/tb/cli" appCommands "github.com/TouchBistro/tb/cli/commands/app" registryCommands "github.com/TouchBistro/tb/cli/commands/registry" @@ -55,9 +57,9 @@ func NewRootCommand(c *cli.Container, version string) *cobra.Command { // Get the user config, pass empty string to have it find the config file cfg, err := config.Read("") if err != nil { - return &cli.ExitError{ - Message: "Failed to load tbrc", - Err: err, + return &fatal.Error{ + Msg: "Failed to load tbrc", + Err: err, } } c.Verbose = opts.verbose || cfg.DebugEnabled() @@ -67,7 +69,7 @@ func NewRootCommand(c *cli.Container, version string) *cobra.Command { if err != nil { return err } - c.Tracker = &progress.SpinnerTracker{ + c.Tracker = &spinner.Tracker{ OutputLogger: c.Logger, PersistMessages: c.Verbose, } @@ -91,7 +93,7 @@ func NewRootCommand(c *cli.Container, version string) *cobra.Command { return nil case "ios": if !util.IsMacOS { - return &cli.ExitError{Message: "tb app ios is only supported on macOS"} + return &fatal.Error{Msg: "tb app ios is only supported on macOS"} } fallthrough case "app", "desktop": @@ -107,9 +109,9 @@ func NewRootCommand(c *cli.Container, version string) *cobra.Command { c.Ctx = progress.ContextWithTracker(cmd.Context(), c.Tracker) c.Engine, err = config.Init(c.Ctx, cfg, initOpts) if err != nil { - return &cli.ExitError{ - Message: "Failed to load registries", - Err: err, + return &fatal.Error{ + Msg: "Failed to load registries", + Err: err, } } return nil diff --git a/cli/commands/up.go b/cli/commands/up.go index 3b9a816..f470782 100644 --- a/cli/commands/up.go +++ b/cli/commands/up.go @@ -5,6 +5,7 @@ import ( "os/exec" "github.com/TouchBistro/goutils/command" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/engine" "github.com/spf13/cobra" @@ -76,9 +77,9 @@ Run the postgres and localstack services directly: SkipGitPull: opts.skipGitPull, }) if err != nil { - return &cli.ExitError{ - Message: "Failed to start services", - Err: err, + return &fatal.Error{ + Msg: "Failed to start services", + Err: err, } } c.Tracker.Info("✔ Started services") @@ -86,18 +87,18 @@ Run the postgres and localstack services directly: if !opts.skipLazydocker { // lazydocker opt in, if it exists it will be launched, otherwise this step will be skipped const lazydocker = "lazydocker" - if command.IsAvailable(lazydocker) { + if command.Exists(lazydocker) { c.Tracker.Debug("Running lazydocker") // Lazydocker doesn't write to stdout or stderr since everything is displaed in the terminal GUI if err := exec.Command(lazydocker).Run(); err != nil { - return &cli.ExitError{ - Message: "Failed running lazydocker", - Err: err, + return &fatal.Error{ + Msg: "Failed running lazydocker", + Err: err, } } } else { // Skip, but inform users about installing it - c.Tracker.Warnf("lazydocker is not insalled. Consider installing it: https://github.com/jesseduffield/lazydocker#installation") + c.Tracker.Warnf("lazydocker is not installed. Consider installing it: https://github.com/jesseduffield/lazydocker#installation") } } c.Tracker.Info("🔈 the containers are running in the background. If you want to terminate them, run tb down") diff --git a/engine/engine.go b/engine/engine.go index 62b9b28..44e3741 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -26,7 +26,7 @@ type Engine struct { baseImages []string loginStrategies []string deviceList simulator.DeviceList - concurrency uint + concurrency int gitClient git.Git dockerClient *docker.Docker @@ -64,7 +64,7 @@ type Options struct { DeviceList simulator.DeviceList // Concurrency controls how many goroutines can run concurrently. // Defaults to runtime.NumCPU if omitted. - Concurrency uint + Concurrency int // GitClient is the client to use for git operations. // This allows for overriding the default git client if provided. GitClient git.Git diff --git a/engine/service.go b/engine/service.go index acb81ee..de38171 100644 --- a/engine/service.go +++ b/engine/service.go @@ -187,11 +187,10 @@ func (e *Engine) Up(ctx context.Context, opts UpOptions) error { // Do this serially since we had issues before when trying to do it in parallel. // TODO(@cszatmary): Should scope what the deal was and see if we do these in parallel. // We might need to rethink the whole way pre-run works. - err := progress.Run(ctx, progress.RunOptions{}, func(ctx context.Context) error { - // Manually start the spinner so we can keep track of progress. - // This will override the one created by this Run call. - tracker.Start("Performing pre-run step for services (this may take a long time)", len(services)) - + err := progress.Run(ctx, progress.RunOptions{ + Message: "Performing pre-run step for services (this may take a long time)", + Count: len(services), + }, func(ctx context.Context) error { for _, s := range services { if s.PreRun == "" { tracker.Debugf("No pre-run for %s, skipping", s.FullName()) diff --git a/go.mod b/go.mod index 9593e1c..69e4105 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/TouchBistro/tb go 1.17 require ( - github.com/TouchBistro/goutils v0.2.0 + github.com/TouchBistro/goutils v0.3.0 github.com/aws/aws-sdk-go-v2 v1.11.2 github.com/aws/aws-sdk-go-v2/config v1.11.0 github.com/aws/aws-sdk-go-v2/service/ecr v1.11.1 @@ -13,7 +13,6 @@ require ( github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v20.10.12+incompatible github.com/matryer/is v1.4.0 - github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.3.0 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b @@ -59,6 +58,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.10.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect diff --git a/go.sum b/go.sum index e8f08b0..b40f802 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,12 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/TouchBistro/goutils v0.2.0 h1:UF1gDr5JCpN9mC301eaSK2X4Ch1B1ZONEGTtyZTi25g= -github.com/TouchBistro/goutils v0.2.0/go.mod h1:Z4vmIj97FCumPsWY9w/h+KgTglEUDJjTySWP7RgUIJ4= +github.com/TouchBistro/goutils v0.2.1-0.20220117001054-efef9eb368e2 h1:ezxe2++GMN4vz/Y2nTIT6fsAAUFLeTnMrK4GKOjYQuA= +github.com/TouchBistro/goutils v0.2.1-0.20220117001054-efef9eb368e2/go.mod h1:Z4vmIj97FCumPsWY9w/h+KgTglEUDJjTySWP7RgUIJ4= +github.com/TouchBistro/goutils v0.2.1-0.20220120212605-0560d8656f2a h1:AlOxfeaTXFu0rqyblS329i0TFMiwsDk610ssa5G0nUM= +github.com/TouchBistro/goutils v0.2.1-0.20220120212605-0560d8656f2a/go.mod h1:Z4vmIj97FCumPsWY9w/h+KgTglEUDJjTySWP7RgUIJ4= +github.com/TouchBistro/goutils v0.3.0 h1:k1LNPCJbcC/tkbG14nplU9OxSMSzjLCcgHMn8NIVDqQ= +github.com/TouchBistro/goutils v0.3.0/go.mod h1:Z4vmIj97FCumPsWY9w/h+KgTglEUDJjTySWP7RgUIJ4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= diff --git a/main.go b/main.go index 7feb930..8716ae5 100644 --- a/main.go +++ b/main.go @@ -6,8 +6,8 @@ import ( "fmt" "os" "os/signal" - "strings" + "github.com/TouchBistro/goutils/fatal" "github.com/TouchBistro/tb/cli" "github.com/TouchBistro/tb/cli/commands" "github.com/TouchBistro/tb/resource" @@ -41,50 +41,35 @@ func main() { } // Handle error - var exitErr *cli.ExitError + var fatalErr *fatal.Error switch { - case errors.As(err, &exitErr): - // Nothing to do, since exitErr is now populated + case errors.As(err, &fatalErr): + // Nothing to do, since fatalErr is now populated case errors.Is(err, context.Canceled): - exitErr = &cli.ExitError{ - Code: 130, - Message: "\nOperation cancelled", + fatalErr = &fatal.Error{ + Code: 130, + Msg: "\nOperation cancelled", } case errors.Is(err, resource.ErrNotFound): - exitErr = &cli.ExitError{ - Message: "Try running `tb list` to see available services", - Err: err, + fatalErr = &fatal.Error{ + Msg: "Try running `tb list` to see available services", + Err: err, } default: // TODO(@cszatmary): We can check if errors.Error and use the Kind // to add custom messages to try and help the user. // We should also add sepecific error codes based on Kind. - exitErr = &cli.ExitError{Err: err} - } - // Make sure a valid exit code was set, if not just default to 1 - // since that's the general catch all error code. - if exitErr.Code <= 0 { - exitErr.Code = 1 + fatalErr = &fatal.Error{Err: err} } - // Print out the error and message then exit - if exitErr.Err != nil { + // Print the error ourselves instead of letting fatal do it since we want + // to do some additional stuff after before exiting. + if fatalErr.Msg != "" || fatalErr.Err != nil { if c.Verbose { - fmt.Fprintf(os.Stderr, "Error: %+v\n", exitErr.Err) + fmt.Fprintf(os.Stderr, "%+v\n", fatalErr) } else { - fmt.Fprintf(os.Stderr, "Error: %s\n", exitErr.Err) - } - } - // If an error was just printed and a message is going to be printed, - // add an extra newline inbetween them - if exitErr.Err != nil && exitErr.Message != "" { - fmt.Fprintln(os.Stderr) - } - if exitErr.Message != "" { - fmt.Fprint(os.Stderr, exitErr.Message) - if !strings.HasSuffix(exitErr.Message, "\n") { - fmt.Fprintln(os.Stderr) + fmt.Fprintf(os.Stderr, "%v\n", fatalErr) } } @@ -92,5 +77,5 @@ func main() { if name := c.Logger.Filename(); name != "" { fmt.Fprintf(os.Stderr, "\n👉 Logs are available at: %s 👈\n", name) } - os.Exit(exitErr.Code) + fatal.Exit(fatalErr) } diff --git a/registry/registry.go b/registry/registry.go index a9143d0..3dfb231 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -215,19 +215,35 @@ type ValidateResult struct { // See each field for more details. func Validate(path string, opts ValidateOptions) ValidateResult { const op = errors.Op("registry.Validate") + + // We need a valid registry name for validation to work. For the "org" we can just use "local". + // Resolve the absolute path ourselves so we can get the name of the registry directory. + // This way if you are in the `my-registry` dir and pass `.` you'd get + // `local/my-registry` as the registry name, not `local/.` which would break. + absPath, err := filepath.Abs(path) + if err != nil { + err = errors.Wrap(err, errors.Meta{ + Kind: errkind.IO, + Reason: "unable to resolve absolute path to registry", + Op: op, + }) + // We can just return this error for every single one. + return ValidateResult{err, err, err} + } + r := Registry{ - // Name needs to be a proper registry name so just say the org is local - Name: "local/" + filepath.Base(path), - Path: path, + Name: "local/" + filepath.Base(absPath), + Path: absPath, } var result ValidateResult if opts.Logger == nil { opts.Logger = progress.NoopTracker{} } + opts.Logger.Debugf("Validating registry %s", r.Name) // Validate apps.yml opts.Logger.Debug("Validating apps") - err := readApps(op, r, readAppsOptions{ + err = readApps(op, r, readAppsOptions{ iosCollection: &app.Collection{}, desktopCollection: &app.Collection{}, }) diff --git a/registry/registry_test.go b/registry/registry_test.go index e98c438..af919dc 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -276,6 +276,17 @@ func TestValidate(t *testing.T) { is.NoErr(result.ServicesErr) } +func TestValidateNormalizePath(t *testing.T) { + is := is.New(t) + // Make sure the path is normalized and it correctly resolves the registry name using + // the base name, i.e. `local/registry-2`. Before there was a bug where the last part of the + // path was taken as is and it lead to issues because `local/.` is not a valid registry name. + result := registry.Validate("testdata/registry-2/.", registry.ValidateOptions{}) + is.NoErr(result.AppsErr) + is.NoErr(result.PlaylistsErr) + is.NoErr(result.ServicesErr) +} + func TestValidateErrors(t *testing.T) { is := is.New(t) result := registry.Validate("testdata/invalid-registry-1", registry.ValidateOptions{ diff --git a/resource/playlist/playlist.go b/resource/playlist/playlist.go index 5153c38..1d1bcdb 100644 --- a/resource/playlist/playlist.go +++ b/resource/playlist/playlist.go @@ -99,7 +99,7 @@ func (c *Collection) SetCustom(p Playlist) { // // If a dependency cycle is detected while resolving extends an error will be returned. func (c *Collection) ServiceNames(playlistName string) ([]string, error) { - const op = errors.Op("paylist.Collection.ServiceNames") + const op = errors.Op("playlist.Collection.ServiceNames") serviceNames, err := c.resolveServiceNames(op, playlistName, make(map[string]bool)) if err != nil { return nil, err