From 8e9055f2a0d3c8c99b8f10e39c9a6ee6e4a4eccd Mon Sep 17 00:00:00 2001 From: George Sedky Date: Sun, 7 May 2023 19:08:05 +0000 Subject: [PATCH] Improve error handling and authentication error messages --- cmd/devx/auth.go | 22 +++++++++++++++ cmd/devx/build.go | 2 +- cmd/devx/diff.go | 2 +- cmd/devx/main.go | 35 +++++++++++++++++++++-- cmd/devx/project.go | 2 +- pkg/auth/auth.go | 63 ++++++++++++++++++++++++++++++++++++++++-- pkg/catalog/catalog.go | 2 +- pkg/client/client.go | 14 +++++----- pkg/project/project.go | 31 ++++++++++----------- pkg/utils/utils.go | 12 ++++++-- 10 files changed, 150 insertions(+), 35 deletions(-) diff --git a/cmd/devx/auth.go b/cmd/devx/auth.go index 2a72bae..9ff88fe 100644 --- a/cmd/devx/auth.go +++ b/cmd/devx/auth.go @@ -15,3 +15,25 @@ var loginCmd = &cobra.Command{ return nil }, } + +var clearCmd = &cobra.Command{ + Use: "clear", + Short: "Clear cached credentials", + RunE: func(cmd *cobra.Command, args []string) error { + if err := auth.Clear(server); err != nil { + return err + } + return nil + }, +} + +var infoCmd = &cobra.Command{ + Use: "info", + Short: "Display session information", + RunE: func(cmd *cobra.Command, args []string) error { + if err := auth.Info(server); err != nil { + return err + } + return nil + }, +} diff --git a/cmd/devx/build.go b/cmd/devx/build.go index 5f2522f..d5bb1d4 100644 --- a/cmd/devx/build.go +++ b/cmd/devx/build.go @@ -14,7 +14,7 @@ var buildCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Aliases: []string{"do"}, RunE: func(cmd *cobra.Command, args []string) error { - if err := client.Run(args[0], configDir, stackPath, buildersPath, reserve, dryRun, server, strict, stdout); err != nil { + if err := client.Run(args[0], configDir, stackPath, buildersPath, reserve, dryRun, server, noStrict, stdout); err != nil { return fmt.Errorf(errors.Details(err, nil)) } return nil diff --git a/cmd/devx/diff.go b/cmd/devx/diff.go index 6fa3fe5..43a1b14 100644 --- a/cmd/devx/diff.go +++ b/cmd/devx/diff.go @@ -13,7 +13,7 @@ var diffCmd = &cobra.Command{ Short: "Diff the current stack with that @ target (e.g. HEAD, commit, tag).", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - if err := client.Diff(args[0], args[1], configDir, stackPath, buildersPath, server, strict); err != nil { + if err := client.Diff(args[0], args[1], configDir, stackPath, buildersPath, server, noStrict); err != nil { return fmt.Errorf(errors.Details(err, nil)) } return nil diff --git a/cmd/devx/main.go b/cmd/devx/main.go index 71d3e2c..91f668e 100644 --- a/cmd/devx/main.go +++ b/cmd/devx/main.go @@ -3,9 +3,12 @@ package main import ( "encoding/json" "fmt" + "net/http" "os" + "strings" "github.com/devopzilla/guku-devx/pkg/auth" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -18,7 +21,7 @@ var ( showTransformers bool dryRun bool noColor bool - strict bool + noStrict bool verbosity string stdout bool reserve bool @@ -44,7 +47,7 @@ func init() { rootCmd.PersistentFlags().StringVarP(&stackPath, "stack", "s", "stack", "stack field name in config file") rootCmd.PersistentFlags().StringVarP(&buildersPath, "builders", "b", "builders", "builders field name in config file") rootCmd.PersistentFlags().BoolVar(&noColor, "no-color", false, "disable colors") - rootCmd.PersistentFlags().BoolVarP(&strict, "strict", "S", false, "make sure all traits are fulfilled by at least one flow") + rootCmd.PersistentFlags().BoolVarP(&noStrict, "no-strict", "S", false, "ignore traits not fulfilled by a builder") buildCmd.PersistentFlags().BoolVarP(&reserve, "reserve", "r", false, "reserve build resources") buildCmd.PersistentFlags().BoolVarP(&dryRun, "dry-run", "d", false, "output the entire stack after transformation without applying drivers") buildCmd.PersistentFlags().BoolVarP(&stdout, "stdout", "o", false, "output result to stdout") @@ -95,13 +98,18 @@ func init() { publishCatalogCmd, publishModuleCmd, ) + + loginCmd.AddCommand( + clearCmd, + infoCmd, + ) } var rootCmd = &cobra.Command{ Use: "devx", Short: "guku DevX cloud native self-service magic", SilenceUsage: true, - PersistentPreRun: setupLogging, + PersistentPreRun: preRun, } var versionCmd = &cobra.Command{ @@ -120,6 +128,27 @@ var versionCmd = &cobra.Command{ }, } +func preRun(cmd *cobra.Command, args []string) { + setupLogging(cmd, args) + + resp, err := http.Get("https://api.github.com/repos/devopzilla/guku-devx/releases?per_page=1") + if err == nil { + releases := []struct { + TagName string `json:"tag_name"` + }{} + + json.NewDecoder(resp.Body).Decode(&releases) + + if len(releases) > 0 { + latestVersion := strings.TrimPrefix(releases[0].TagName, "v") + if latestVersion != version && version != "DEV" { + log.Infof("A newer version of DevX \"v%s\" is available, please upgrade!\n", latestVersion) + } + } + + } +} + func main() { if err := rootCmd.Execute(); err != nil { os.Exit(1) diff --git a/cmd/devx/project.go b/cmd/devx/project.go index 391f712..b541e8a 100644 --- a/cmd/devx/project.go +++ b/cmd/devx/project.go @@ -50,7 +50,7 @@ var validateCmd = &cobra.Command{ Aliases: []string{"v"}, Short: "Validate configurations", RunE: func(cmd *cobra.Command, args []string) error { - if err := project.Validate(configDir, stackPath, buildersPath, strict); err != nil { + if err := project.Validate(configDir, stackPath, buildersPath, noStrict); err != nil { return fmt.Errorf(errors.Details(err, nil)) } return nil diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 0368558..add2ae2 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -70,13 +70,50 @@ func GetDefaultToken() (string, *string, error) { return tenant, cfg.Token, nil } +func Clear(server ServerConfig) error { + return deleteConfig() +} + +func Info(server ServerConfig) error { + var cfg Config + + if server.Tenant == "" { + tenant, _, err := loadDefaultConfig() + if err != nil { + return err + } + server.Tenant = tenant + } + + cfgFile, err := loadConfig() + if err != nil { + return err + } + + if _, ok := cfgFile[server.Tenant]; ok { + cfg = cfgFile[server.Tenant] + } + + log.Infof(`Auth info: + Tenant: %s + Server endpoint: %s + Token valid until: %s + `, + server.Tenant, + *cfg.Endpoint, + cfg.TokenExpiresOn, + ) + + return nil +} + func Login(server ServerConfig) error { if server.Endpoint == "" { server.Endpoint = DEVX_CLOUD_ENDPOINT } if server.Tenant == "" { - return fmt.Errorf("--tenant is required") + return fmt.Errorf("--tenant is required") } cfgFile, err := loadConfig() @@ -212,7 +249,7 @@ func loadDefaultConfig() (string, Config, error) { return tenant, cfg, nil } } - return "", Config{}, fmt.Errorf("no configs found. login and try again") + return "", Config{}, fmt.Errorf("no credentials found\nTry logging in using\n\ndevx login --tenant ") } func loadConfig() (ConfigFile, error) { @@ -261,3 +298,25 @@ func saveConfig(cfg ConfigFile) error { return os.WriteFile(configPath, configData, 0700) } + +func deleteConfig() error { + configDir, err := getConfigDir() + if err != nil { + return err + } + + if _, err := os.Stat(configDir); os.IsNotExist(err) { + err = os.MkdirAll(configDir, 0700) + if err != nil { + return err + } + } + + configPath := filepath.Join(configDir, "config") + + if err := os.Remove(configPath); err != nil { + return err + } + + return nil +} diff --git a/pkg/catalog/catalog.go b/pkg/catalog/catalog.go index fb5de5c..cb85b54 100644 --- a/pkg/catalog/catalog.go +++ b/pkg/catalog/catalog.go @@ -57,7 +57,7 @@ type ModuleCUE struct { func PublishModule(gitDir string, configDir string, server auth.ServerConfig, tags []string) error { gitData, err := gitrepo.GetGitData(gitDir) if err != nil { - return nil + return err } tagsToPush := []string{} diff --git a/pkg/client/client.go b/pkg/client/client.go index 93d1981..e820a8e 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -21,12 +21,12 @@ import ( "github.com/devopzilla/guku-devx/pkg/utils" ) -func Run(environment string, configDir string, stackPath string, buildersPath string, reserve bool, dryRun bool, server auth.ServerConfig, strict bool, stdout bool) error { +func Run(environment string, configDir string, stackPath string, buildersPath string, reserve bool, dryRun bool, server auth.ServerConfig, noStrict bool, stdout bool) error { ctx := context.Background() ctx = context.WithValue(ctx, utils.ConfigDirKey, configDir) ctx = context.WithValue(ctx, utils.DryRunKey, dryRun) - stack, builder, err := buildStack(ctx, environment, configDir, stackPath, buildersPath, strict) + stack, builder, err := buildStack(ctx, environment, configDir, stackPath, buildersPath, noStrict) if err != nil { return err } @@ -63,7 +63,7 @@ func Run(environment string, configDir string, stackPath string, buildersPath st return nil } -func Diff(target string, environment string, configDir string, stackPath string, buildersPath string, server auth.ServerConfig, strict bool) error { +func Diff(target string, environment string, configDir string, stackPath string, buildersPath string, server auth.ServerConfig, noStrict bool) error { log.Infof("📍 Processing target stack @ %s", target) targetDir, err := os.MkdirTemp("", "devx-target-*") if err != nil { @@ -103,7 +103,7 @@ func Diff(target string, environment string, configDir string, stackPath string, targetCtx := context.Background() targetCtx = context.WithValue(targetCtx, utils.ConfigDirKey, targetDir) targetCtx = context.WithValue(targetCtx, utils.DryRunKey, true) - targetStack, _, err := buildStack(targetCtx, environment, targetDir, stackPath, buildersPath, strict) + targetStack, _, err := buildStack(targetCtx, environment, targetDir, stackPath, buildersPath, noStrict) if err != nil { return err } @@ -112,7 +112,7 @@ func Diff(target string, environment string, configDir string, stackPath string, currentCtx := context.Background() currentCtx = context.WithValue(currentCtx, utils.ConfigDirKey, configDir) currentCtx = context.WithValue(currentCtx, utils.DryRunKey, true) - currentStack, _, err := buildStack(currentCtx, environment, configDir, stackPath, buildersPath, strict) + currentStack, _, err := buildStack(currentCtx, environment, configDir, stackPath, buildersPath, noStrict) if err != nil { return err } @@ -160,7 +160,7 @@ func Diff(target string, environment string, configDir string, stackPath string, return nil } -func buildStack(ctx context.Context, environment string, configDir string, stackPath string, buildersPath string, strict bool) (*stack.Stack, *stackbuilder.StackBuilder, error) { +func buildStack(ctx context.Context, environment string, configDir string, stackPath string, buildersPath string, noStrict bool) (*stack.Stack, *stackbuilder.StackBuilder, error) { log.Infof("🏗️ Loading stack...") overlays, err := utils.GetOverlays(configDir) if err != nil { @@ -169,7 +169,7 @@ func buildStack(ctx context.Context, environment string, configDir string, stack value, stackId, depIds := utils.LoadProject(configDir, &overlays) log.Info("👀 Validating stack...") - err = project.ValidateProject(value, stackPath, buildersPath, strict) + err = project.ValidateProject(value, stackPath, buildersPath, noStrict) if err != nil { return nil, nil, err } diff --git a/pkg/project/project.go b/pkg/project/project.go index 2b82305..f0caf8c 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -34,14 +34,14 @@ import ( const stakpakPrefix = "stakpak://" -func Validate(configDir string, stackPath string, buildersPath string, strict bool) error { +func Validate(configDir string, stackPath string, buildersPath string, noStrict bool) error { overlays, err := utils.GetOverlays(configDir) if err != nil { return err } value, _, _ := utils.LoadProject(configDir, &overlays) - if err := ValidateProject(value, stackPath, buildersPath, strict); err != nil { + if err := ValidateProject(value, stackPath, buildersPath, noStrict); err != nil { return err } @@ -49,7 +49,7 @@ func Validate(configDir string, stackPath string, buildersPath string, strict bo return nil } -func ValidateProject(value cue.Value, stackPath string, buildersPath string, strict bool) error { +func ValidateProject(value cue.Value, stackPath string, buildersPath string, noStrict bool) error { err := value.Validate() if err != nil { return err @@ -77,24 +77,21 @@ func ValidateProject(value cue.Value, stackPath string, buildersPath string, str return err } - if strict { - builders, err := stackbuilder.NewEnvironments(value.LookupPath(cue.ParsePath(buildersPath))) - if err != nil { - return err - } + if noStrict { + return nil + } - stack, err := stack.NewStack(stackValue, "", []string{}) - if err != nil { - return err - } + builders, err := stackbuilder.NewEnvironments(value.LookupPath(cue.ParsePath(buildersPath))) + if err != nil { + return err + } - err = stackbuilder.CheckTraitFulfillment(builders, stack) - if err != nil { - return err - } + stack, err := stack.NewStack(stackValue, "", []string{}) + if err != nil { + return err } - return nil + return stackbuilder.CheckTraitFulfillment(builders, stack) } func Discover(configDir string, showDefs bool, showTransformers bool) error { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index ba5428d..a4b811e 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -393,7 +393,11 @@ func SendData(server auth.ServerConfig, apiPath string, data interface{}) ([]byt log.Fatalf("failed to parse error response body: %s", body) } - return body, errors.New(errResponse.Message) + if response.StatusCode == 401 { + return nil, fmt.Errorf("authentication failed with error message: %s\nTry logging in using\n\ndevx login --tenant ", errResponse.Message) + } + + return nil, errors.New(errResponse.Message) } return body, nil @@ -451,7 +455,11 @@ func GetData(server auth.ServerConfig, apiPath string, id *string, query map[str log.Fatalf("failed to parse error response body: %s", body) } - return body, errors.New(errResponse.Message) + if response.StatusCode == 401 { + return nil, fmt.Errorf("authentication failed with error message: %s\nTry logging in using\n\ndevx login --tenant ", errResponse.Message) + } + + return nil, errors.New(errResponse.Message) } return body, nil