From 4eac16c95cd2ae8573c2cf719bfc520534ae00c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Parada?= Date: Fri, 3 Dec 2021 12:17:11 -0300 Subject: [PATCH] config cmd --- cmd/calyptia/config.go | 18 ++++ cmd/calyptia/config_project.go | 136 ++++++++++++++++++++++++++++++ cmd/calyptia/create_aggregator.go | 9 +- cmd/calyptia/delete_agent.go | 9 +- cmd/calyptia/get_agent.go | 13 +-- cmd/calyptia/get_aggregator.go | 9 +- cmd/calyptia/main.go | 19 ++++- cmd/calyptia/top_project.go | 15 +++- cmd/calyptia/update_project.go | 14 ++- 9 files changed, 218 insertions(+), 24 deletions(-) create mode 100644 cmd/calyptia/config.go create mode 100644 cmd/calyptia/config_project.go diff --git a/cmd/calyptia/config.go b/cmd/calyptia/config.go new file mode 100644 index 00000000..44f8ce14 --- /dev/null +++ b/cmd/calyptia/config.go @@ -0,0 +1,18 @@ +package main + +import "github.com/spf13/cobra" + +func newCmdConfig(config *config) *cobra.Command { + cmd := &cobra.Command{ + Use: "config", + Short: "Configure Calyptia CLI", + } + + cmd.AddCommand( + newCmdConfigSetProject(config), + newCmdConfigCurrentProject(config), + newCmdConfigUnsetProject(config), + ) + + return cmd +} diff --git a/cmd/calyptia/config_project.go b/cmd/calyptia/config_project.go new file mode 100644 index 00000000..79c7f6c4 --- /dev/null +++ b/cmd/calyptia/config_project.go @@ -0,0 +1,136 @@ +package main + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" +) + +func newCmdConfigSetProject(config *config) *cobra.Command { + return &cobra.Command{ + Use: "set_project PROJECT", + Short: "Set the default project so you don't have to specify it on all commands", + Args: cobra.ExactArgs(1), + ValidArgsFunction: config.completeProjects, + RunE: func(cmd *cobra.Command, args []string) error { + projectKey := args[0] + projectID := projectKey + { + pp, err := config.cloud.Projects(config.ctx, 0) + if err != nil { + return err + } + + a, ok := findProjectByName(pp, projectKey) + if !ok && !validUUID(projectID) { + return fmt.Errorf("could not find project %q", projectKey) + } + + if ok { + projectID = a.ID + } + } + + if err := saveDefaultProject(projectID); err != nil { + return err + } + + config.defaultProject = projectID + + return nil + }, + } +} + +func newCmdConfigCurrentProject(config *config) *cobra.Command { + return &cobra.Command{ + Use: "current_project", + Short: "Get the current configured default project", + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println(config.defaultProject) + return nil + }, + } +} + +func newCmdConfigUnsetProject(config *config) *cobra.Command { + return &cobra.Command{ + Use: "unset_project", + Short: "Unset the current configured default project", + RunE: func(cmd *cobra.Command, args []string) error { + if err := deleteSavedDefaultProject(); err != nil { + return err + } + + config.defaultProject = "" + return nil + }, + } +} + +func saveDefaultProject(projectKey string) error { + home, err := os.UserHomeDir() + if err != nil { + return err + } + + fileName := filepath.Join(home, ".calyptia", "default_project") + if _, err := os.Stat(fileName); os.IsNotExist(err) { + err = os.MkdirAll(filepath.Dir(fileName), fs.ModePerm) + if err != nil { + return fmt.Errorf("could not create directory: %w", err) + } + } + + err = os.WriteFile(fileName, []byte(projectKey), fs.ModePerm) + if err != nil { + return fmt.Errorf("could not store default project: %w", err) + } + + return nil +} + +var errDefaultProjectNotFound = errors.New("default project not found") + +func savedDefaultProject() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("could not get user home dir: %w", err) + } + + b, err := readFile(filepath.Join(home, ".calyptia", "default_project")) + if os.IsNotExist(err) || errors.Is(err, fs.ErrNotExist) { + return "", errDefaultProjectNotFound + } + + if err != nil { + return "", err + } + + projectKey := strings.TrimSpace(string(b)) + return projectKey, nil +} + +func deleteSavedDefaultProject() error { + home, err := os.UserHomeDir() + if err != nil { + return err + } + + fileName := filepath.Join(home, ".calyptia", "default_project") + if _, err := os.Stat(fileName); os.IsNotExist(err) { + return nil + } + + err = os.Remove(fileName) + if err != nil { + return fmt.Errorf("could not delete default project: %w", err) + } + + return nil +} diff --git a/cmd/calyptia/create_aggregator.go b/cmd/calyptia/create_aggregator.go index 315b01b2..206ed203 100644 --- a/cmd/calyptia/create_aggregator.go +++ b/cmd/calyptia/create_aggregator.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "errors" "fmt" "os" @@ -19,6 +20,10 @@ func newCmdCreateAggregator(config *config) *cobra.Command { Use: "aggregator", Short: "Create a new aggregator", RunE: func(cmd *cobra.Command, args []string) error { + if projectKey == "" { + return errors.New("project required") + } + projectID := projectKey { pp, err := config.cloud.Projects(config.ctx, 0) @@ -66,14 +71,12 @@ func newCmdCreateAggregator(config *config) *cobra.Command { } fs := cmd.Flags() - fs.StringVar(&projectKey, "project", "", "Parent project ID or name") + fs.StringVar(&projectKey, "project", config.defaultProject, "Parent project ID or name") fs.StringVar(&name, "name", "", "Aggregator name; leave it empty to generate a random name") fs.StringVarP(&format, "output-format", "f", "table", "Output format. Allowed: table, json") _ = cmd.RegisterFlagCompletionFunc("project", config.completeProjects) _ = cmd.RegisterFlagCompletionFunc("output-format", config.completeOutputFormat) - _ = cmd.MarkFlagRequired("project") // TODO: use default project ID from config cmd. - return cmd } diff --git a/cmd/calyptia/delete_agent.go b/cmd/calyptia/delete_agent.go index 7c110962..e21cc13f 100644 --- a/cmd/calyptia/delete_agent.go +++ b/cmd/calyptia/delete_agent.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "strings" "time" @@ -79,6 +80,10 @@ func newCmdDeleteAgents(config *config) *cobra.Command { Use: "agents", Short: "Delete many agents from a project", RunE: func(cmd *cobra.Command, args []string) error { + if projectKey == "" { + return errors.New("project required") + } + projectID := projectKey { pp, err := config.cloud.Projects(config.ctx, 0) @@ -156,13 +161,11 @@ func newCmdDeleteAgents(config *config) *cobra.Command { } fs := cmd.Flags() - fs.StringVar(&projectKey, "project", "", "Delete agents from this project ID or name") + fs.StringVar(&projectKey, "project", config.defaultProject, "Delete agents from this project ID or name") fs.BoolVar(&inactive, "inactive", true, "Delete inactive agents only") fs.BoolVarP(&confirmed, "yes", "y", false, "Confirm deletion") _ = cmd.RegisterFlagCompletionFunc("project", config.completeProjects) - _ = cmd.MarkFlagRequired("project") // TODO: use default project ID from config cmd. - return cmd } diff --git a/cmd/calyptia/get_agent.go b/cmd/calyptia/get_agent.go index 04fbb5f4..1a631894 100644 --- a/cmd/calyptia/get_agent.go +++ b/cmd/calyptia/get_agent.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "errors" "fmt" "os" "sync" @@ -18,13 +19,17 @@ import ( ) func newCmdGetAgents(config *config) *cobra.Command { - var format string var projectKey string var last uint64 + var format string cmd := &cobra.Command{ Use: "agents", Short: "Display latest agents from a project", RunE: func(cmd *cobra.Command, args []string) error { + if projectKey == "" { + return errors.New("project required") + } + projectID := projectKey { pp, err := config.cloud.Projects(config.ctx, 0) @@ -73,15 +78,13 @@ func newCmdGetAgents(config *config) *cobra.Command { } fs := cmd.Flags() - fs.StringVarP(&format, "output-format", "o", "table", "Output format. Allowed: table, json") - fs.StringVar(&projectKey, "project", "", "Parent project ID or name") + fs.StringVar(&projectKey, "project", config.defaultProject, "Parent project ID or name") fs.Uint64VarP(&last, "last", "l", 0, "Last `N` agents. 0 means no limit") + fs.StringVarP(&format, "output-format", "o", "table", "Output format. Allowed: table, json") _ = cmd.RegisterFlagCompletionFunc("output-format", config.completeOutputFormat) _ = cmd.RegisterFlagCompletionFunc("project", config.completeProjects) - _ = cmd.MarkFlagRequired("project") // TODO: use default project ID from config cmd. - return cmd } diff --git a/cmd/calyptia/get_aggregator.go b/cmd/calyptia/get_aggregator.go index 457f74f4..1d2c622b 100644 --- a/cmd/calyptia/get_aggregator.go +++ b/cmd/calyptia/get_aggregator.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "errors" "fmt" "os" "sync" @@ -21,6 +22,10 @@ func newCmdGetAggregators(config *config) *cobra.Command { Use: "aggregators", Short: "Display latest aggregators from a project", RunE: func(cmd *cobra.Command, args []string) error { + if projectKey == "" { + return errors.New("project required") + } + projectID := projectKey { pp, err := config.cloud.Projects(config.ctx, 0) @@ -70,14 +75,12 @@ func newCmdGetAggregators(config *config) *cobra.Command { fs := cmd.Flags() fs.StringVarP(&format, "output-format", "o", "table", "Output format. Allowed: table, json") - fs.StringVar(&projectKey, "project", "", "Parent project ID or name") + fs.StringVar(&projectKey, "project", config.defaultProject, "Parent project ID or name") fs.Uint64VarP(&last, "last", "l", 0, "Last `N` aggregators. 0 means no limit") _ = cmd.RegisterFlagCompletionFunc("output-format", config.completeOutputFormat) _ = cmd.RegisterFlagCompletionFunc("project", config.completeProjects) - _ = cmd.MarkFlagRequired("project") // TODO: use default project ID from config cmd. - return cmd } diff --git a/cmd/calyptia/main.go b/cmd/calyptia/main.go index 6a110bc8..22b0c88b 100644 --- a/cmd/calyptia/main.go +++ b/cmd/calyptia/main.go @@ -67,6 +67,17 @@ func newCmd(ctx context.Context) *cobra.Command { // Now all requests will be authenticated and the token refreshes by its own. config.cloud.HTTPClient = config.auth0.Client(config.ctx, tok) + }, func() { + if config.defaultProject != "" { + return + } + + projectKey, err := savedDefaultProject() + if err == errDefaultProjectNotFound { + return + } + + config.defaultProject = projectKey }) cmd := &cobra.Command{ Use: "calyptia", @@ -82,6 +93,7 @@ func newCmd(ctx context.Context) *cobra.Command { cmd.AddCommand( newCmdLogin(config), + newCmdConfig(config), newCmdCreate(config), newCmdGet(config), newCmdUpdate(config), @@ -93,9 +105,10 @@ func newCmd(ctx context.Context) *cobra.Command { } type config struct { - ctx context.Context - auth0 *auth0.Client - cloud *cloudclient.Client + ctx context.Context + auth0 *auth0.Client + cloud *cloudclient.Client + defaultProject string } func env(key, fallback string) string { diff --git a/cmd/calyptia/top_project.go b/cmd/calyptia/top_project.go index 65204001..d4bd369e 100644 --- a/cmd/calyptia/top_project.go +++ b/cmd/calyptia/top_project.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "fmt" "io" "math" @@ -40,13 +41,19 @@ func newCmdTopProject(config *config) *cobra.Command { var start, interval time.Duration var last uint64 cmd := &cobra.Command{ - Use: "project PROJECT", + Use: "project [PROJECT]", Short: "Display metrics from a project", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), ValidArgsFunction: config.completeProjects, - // TODO: run an interactive "top" program. RunE: func(cmd *cobra.Command, args []string) error { - projectKey := args[0] + projectKey := config.defaultProject + if len(args) > 0 { + projectKey = args[0] + } + if projectKey == "" { + return errors.New("project required") + } + initialModel := &Model{ StartingProjectKey: projectKey, ProjectModel: NewProjectModel(config.ctx, config.cloud, projectKey, start, interval, last), diff --git a/cmd/calyptia/update_project.go b/cmd/calyptia/update_project.go index 26439635..c05c6fac 100644 --- a/cmd/calyptia/update_project.go +++ b/cmd/calyptia/update_project.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "github.com/calyptia/cloud" @@ -11,16 +12,23 @@ func newCmdUpdateProject(config *config) *cobra.Command { var newName string cmd := &cobra.Command{ - Use: "project PROJECT", + Use: "project [PROJECT]", Short: "Update a single project by ID or name", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), ValidArgsFunction: config.completeProjects, RunE: func(cmd *cobra.Command, args []string) error { if newName == "" { return nil } - projectKey := args[0] + projectKey := config.defaultProject + if len(args) > 0 { + projectKey = args[0] + } + if projectKey == "" { + return errors.New("project required") + } + projectID := projectKey { if projectKey == newName {