Skip to content

Commit

Permalink
feat: Finalize workspace management (#54)
Browse files Browse the repository at this point in the history
* chore: gofmt

* chore: remove debugging log

* fix: Issue where local config directory doesn't exist

* feat: Validate API key before whoami and workspace commands

* chore: Change "Verifying CLI key..." to "Verifying credentials..."

* chore: Deprecate cli-key flag

* docs: Add documentation about workspace local flag
  • Loading branch information
alexluong authored Jul 5, 2023
1 parent 4a01477 commit bcebadb
Show file tree
Hide file tree
Showing 15 changed files with 85 additions and 37 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,21 @@ Using profile default
Logged in as Me in workspace Yet Another One
```

You can also pin an active workspace in the current working directory with the `--local` flag.

```sh-session
$ hookdeck workspace use --local
Use the arrow keys to navigate: ↓ ↑ → ←
? Select Workspace:
My Workspace
Another Workspace
▸ Yet Another One

Selecting workspace Yet Another One
```

This will create a local config file in your current directory at `myproject/.hookdeck/config.toml`. Depending on your team's Hookdeck usage and workspace setup, you may or may not want to commit this configuration file to version control.

## Developing

Build from source by running:
Expand Down
1 change: 1 addition & 0 deletions pkg/ansi/init_windows.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build windows
// +build windows

package ansi
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,6 @@ func (lc *listenCmd) runListenCmd(cmd *cobra.Command, args []string) error {
}

return listen.Listen(url, source_alias, connection_query, listen.Flags{
NoWSS: lc.noWSS,
NoWSS: lc.noWSS,
}, &Config)
}
3 changes: 2 additions & 1 deletion pkg/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ func init() {
cobra.OnInitialize(Config.InitConfig)

rootCmd.PersistentFlags().StringVarP(&Config.Profile.Name, "profile", "p", "", fmt.Sprintf("profile name (default \"%s\")", hookdeck.DefaultProfileName))
rootCmd.PersistentFlags().StringVar(&Config.Profile.APIKey, "cli-key", "", "Your CLI key to use for the command")
rootCmd.PersistentFlags().StringVar(&Config.Profile.APIKey, "cli-key", "", "(deprecated) Your API key to use for the command")
rootCmd.PersistentFlags().StringVar(&Config.Profile.APIKey, "api-key", "", "Your API key to use for the command")
rootCmd.PersistentFlags().StringVar(&Config.Color, "color", "", "turn on/off color output (on, off, auto)")
rootCmd.PersistentFlags().StringVar(&Config.LocalConfigFile, "config", "", "config file (default is $HOME/.config/hookdeck/config.toml)")
rootCmd.PersistentFlags().StringVar(&Config.DeviceName, "device-name", "", "device name")
Expand Down
6 changes: 5 additions & 1 deletion pkg/cmd/whoami.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ func newWhoamiCmd() *whoamiCmd {
}

func (lc *whoamiCmd) runWhoamiCmd(cmd *cobra.Command, args []string) error {
if err := Config.Profile.ValidateAPIKey(); err != nil {
return err
}

color := ansi.Color(os.Stdout)

fmt.Printf( "Using profile %s\n", color.Bold(Config.Profile.Name))
fmt.Printf("Using profile %s\n", color.Bold(Config.Profile.Name))

response, err := login.ValidateKey(Config.APIBaseURL, Config.Profile.APIKey, Config.Profile.TeamID)
if err != nil {
Expand Down
6 changes: 4 additions & 2 deletions pkg/cmd/workspace_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ func newWorkspaceListCmd() *workspaceListCmd {
return lc
}

func (lc *workspaceListCmd) runWorkspaceListCmd(cmd *cobra.Command, args []string) error {
// TODO: validate API key ??
func (lc *workspaceListCmd) runWorkspaceListCmd(cmd *cobra.Command, args []string) error {
if err := Config.Profile.ValidateAPIKey(); err != nil {
return err
}

workspaces, err := workspace.ListWorkspaces(&Config)
if err != nil {
Expand Down
10 changes: 7 additions & 3 deletions pkg/cmd/workspace_use.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ func newWorkspaceUseCmd() *workspaceUseCmd {
return lc
}

func (lc *workspaceUseCmd) runWorkspaceUseCmd(cmd *cobra.Command, args []string) error {
func (lc *workspaceUseCmd) runWorkspaceUseCmd(cmd *cobra.Command, args []string) error {
if err := Config.Profile.ValidateAPIKey(); err != nil {
return err
}

workspaces, err := workspace.ListWorkspaces(&Config)
if err != nil {
return err
Expand All @@ -45,8 +49,8 @@ func (lc *workspaceUseCmd) runWorkspaceUseCmd(cmd *cobra.Command, args []string)
}

prompt := promptui.Select{
Label: "Select Workspace",
Items: workspaces,
Label: "Select Workspace",
Items: workspaces,
Templates: templates,
}

Expand Down
45 changes: 28 additions & 17 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ const ColorAuto = "auto"

// Config handles all overall configuration for the CLI
type Config struct {
Profile Profile
Color string
LogLevel string
DeviceName string
Profile Profile
Color string
LogLevel string
DeviceName string

// Helpers
APIBaseURL string
Expand All @@ -45,9 +45,9 @@ type Config struct {

// Config
GlobalConfigFile string
GlobalConfig *viper.Viper
GlobalConfig *viper.Viper
LocalConfigFile string
LocalConfig *viper.Viper
LocalConfig *viper.Viper
}

// GetConfigFolder retrieves the folder where the profiles file is stored
Expand Down Expand Up @@ -246,23 +246,34 @@ func (c *Config) RemoveAllProfiles() error {
return c.GlobalConfig.WriteConfig()
}

func (c *Config) SaveLocalConfig() error {
if err := ensureDirectoy(filepath.Dir(c.LocalConfigFile)); err != nil {
return err
}
return c.LocalConfig.WriteConfig()
}

func ensureDirectoy(path string) error {
return os.MkdirAll(path, os.ModePerm)
}

// Construct the config struct from flags > local config > global config
func (c *Config) constructConfig() {
c.Color = getStringConfig([]string{c.Color , c.LocalConfig.GetString("color") , c.GlobalConfig.GetString(("color")) , "auto"})
c.LogLevel = getStringConfig([]string{c.LogLevel , c.LocalConfig.GetString("log") , c.GlobalConfig.GetString(("log")) , "info"})
c.APIBaseURL = getStringConfig([]string{c.APIBaseURL , c.LocalConfig.GetString("api_base") , c.GlobalConfig.GetString(("api_base")) , hookdeck.DefaultAPIBaseURL})
c.DashboardBaseURL = getStringConfig([]string{c.DashboardBaseURL , c.LocalConfig.GetString("dashboard_base") , c.GlobalConfig.GetString(("dashboard_base")) , hookdeck.DefaultDashboardBaseURL})
c.ConsoleBaseURL = getStringConfig([]string{c.ConsoleBaseURL , c.LocalConfig.GetString("console_base") , c.GlobalConfig.GetString(("console_base")) , hookdeck.DefaultConsoleBaseURL})
c.WSBaseURL = getStringConfig([]string{c.WSBaseURL , c.LocalConfig.GetString("ws_base") , c.GlobalConfig.GetString(("ws_base")) , hookdeck.DefaultWebsocektURL})
c.Profile.Name = getStringConfig([]string{c.Profile.Name , c.LocalConfig.GetString("profile") , c.GlobalConfig.GetString(("profile")) , hookdeck.DefaultProfileName})
c.Profile.APIKey = getStringConfig([]string{c.Profile.APIKey , c.LocalConfig.GetString("api_key") , c.GlobalConfig.GetString((c.Profile.GetConfigField("api_key"))) , ""})
c.Profile.TeamID = getStringConfig([]string{c.Profile.TeamID , c.LocalConfig.GetString("workspace_id") , c.LocalConfig.GetString("team_id") , c.GlobalConfig.GetString((c.Profile.GetConfigField("workspace_id"))) , c.GlobalConfig.GetString((c.Profile.GetConfigField("team_id"))) , ""})
c.Profile.TeamMode = getStringConfig([]string{c.Profile.TeamMode , c.LocalConfig.GetString("workspace_mode") , c.LocalConfig.GetString("team_mode") , c.GlobalConfig.GetString((c.Profile.GetConfigField("workspace_mode"))) , c.GlobalConfig.GetString((c.Profile.GetConfigField("team_mode"))) , ""})
c.Color = getStringConfig([]string{c.Color, c.LocalConfig.GetString("color"), c.GlobalConfig.GetString(("color")), "auto"})
c.LogLevel = getStringConfig([]string{c.LogLevel, c.LocalConfig.GetString("log"), c.GlobalConfig.GetString(("log")), "info"})
c.APIBaseURL = getStringConfig([]string{c.APIBaseURL, c.LocalConfig.GetString("api_base"), c.GlobalConfig.GetString(("api_base")), hookdeck.DefaultAPIBaseURL})
c.DashboardBaseURL = getStringConfig([]string{c.DashboardBaseURL, c.LocalConfig.GetString("dashboard_base"), c.GlobalConfig.GetString(("dashboard_base")), hookdeck.DefaultDashboardBaseURL})
c.ConsoleBaseURL = getStringConfig([]string{c.ConsoleBaseURL, c.LocalConfig.GetString("console_base"), c.GlobalConfig.GetString(("console_base")), hookdeck.DefaultConsoleBaseURL})
c.WSBaseURL = getStringConfig([]string{c.WSBaseURL, c.LocalConfig.GetString("ws_base"), c.GlobalConfig.GetString(("ws_base")), hookdeck.DefaultWebsocektURL})
c.Profile.Name = getStringConfig([]string{c.Profile.Name, c.LocalConfig.GetString("profile"), c.GlobalConfig.GetString(("profile")), hookdeck.DefaultProfileName})
c.Profile.APIKey = getStringConfig([]string{c.Profile.APIKey, c.LocalConfig.GetString("api_key"), c.GlobalConfig.GetString((c.Profile.GetConfigField("api_key"))), ""})
c.Profile.TeamID = getStringConfig([]string{c.Profile.TeamID, c.LocalConfig.GetString("workspace_id"), c.LocalConfig.GetString("team_id"), c.GlobalConfig.GetString((c.Profile.GetConfigField("workspace_id"))), c.GlobalConfig.GetString((c.Profile.GetConfigField("team_id"))), ""})
c.Profile.TeamMode = getStringConfig([]string{c.Profile.TeamMode, c.LocalConfig.GetString("workspace_mode"), c.LocalConfig.GetString("team_mode"), c.GlobalConfig.GetString((c.Profile.GetConfigField("workspace_mode"))), c.GlobalConfig.GetString((c.Profile.GetConfigField("team_mode"))), ""})
}

func getStringConfig(values []string) string {
for _, str := range values {
if str != "" {
if str != "" {
return str
}
}
Expand Down
21 changes: 15 additions & 6 deletions pkg/config/profile.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package config

import "github.com/hookdeck/hookdeck-cli/pkg/validators"

type Profile struct {
Name string // profile name
APIKey string
TeamID string
APIKey string
TeamID string
TeamMode string

Config *Config
Expand All @@ -18,13 +20,13 @@ func (p *Profile) SaveProfile(local bool) error {
// in local, we're d setting mode because it should always be inbound
// as a user can't have both inbound & console teams (i think)
// and we don't need to expose it to the end user
if (local) {
if local {
p.Config.GlobalConfig.Set(p.GetConfigField("api_key"), p.APIKey)
if err := p.Config.GlobalConfig.WriteConfig(); err != nil {
return err
}
p.Config.LocalConfig.Set("workspace_id", p.TeamID)
return p.Config.LocalConfig.WriteConfig()
return p.Config.SaveLocalConfig()
} else {
p.Config.GlobalConfig.Set(p.GetConfigField("api_key"), p.APIKey)
p.Config.GlobalConfig.Set(p.GetConfigField("workspace_id"), p.TeamID)
Expand All @@ -37,11 +39,11 @@ func (p *Profile) RemoveProfile() error {
var err error
runtimeViper := p.Config.GlobalConfig

runtimeViper, err = removeKey(runtimeViper, "profile");
runtimeViper, err = removeKey(runtimeViper, "profile")
if err != nil {
return err
}
runtimeViper, err = removeKey(runtimeViper, p.Name);
runtimeViper, err = removeKey(runtimeViper, p.Name)
if err != nil {
return err
}
Expand All @@ -56,3 +58,10 @@ func (p *Profile) UseProfile() error {
p.Config.GlobalConfig.Set("profile", p.Name)
return p.Config.GlobalConfig.WriteConfig()
}

func (p *Profile) ValidateAPIKey() error {
if p.APIKey == "" {
return validators.ErrAPIKeyNotConfigured
}
return nil
}
4 changes: 1 addition & 3 deletions pkg/listen/listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
)

type Flags struct {
NoWSS bool
NoWSS bool
}

// listenCmd represents the listen command
Expand Down Expand Up @@ -94,8 +94,6 @@ func Listen(URL *url.URL, source_alias string, connection_query string, flags Fl
}
fmt.Println()

fmt.Println(config.WSBaseURL)

p := proxy.New(&proxy.Config{
DeviceName: config.DeviceName,
Key: config.Profile.APIKey,
Expand Down
4 changes: 2 additions & 2 deletions pkg/login/client_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func Login(config *config.Config, input io.Reader) error {
var s *spinner.Spinner

if config.Profile.APIKey != "" {
s = ansi.StartNewSpinner("Verifying CLI Key...", os.Stdout)
s = ansi.StartNewSpinner("Verifying credentials...", os.Stdout)
response, err := ValidateKey(config.APIBaseURL, config.Profile.APIKey, config.Profile.TeamID)
if err != nil {
return err
Expand Down Expand Up @@ -150,7 +150,7 @@ func CILogin(config *config.Config, apiKey string, name string) error {

client := &hookdeck.Client{
BaseURL: parsedBaseURL,
APIKey: apiKey,
APIKey: apiKey,
}

deviceName := name
Expand Down
1 change: 1 addition & 0 deletions pkg/useragent/uname_unix.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows

package useragent
Expand Down
1 change: 1 addition & 0 deletions pkg/useragent/uname_unix_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows

package useragent
Expand Down
1 change: 1 addition & 0 deletions pkg/useragent/uname_windows.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build windows
// +build windows

package useragent
Expand Down
2 changes: 1 addition & 1 deletion pkg/workspace/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ func ListWorkspaces(config *config.Config) ([]hookdeck.Workspace, error) {
}

return client.ListWorkspaces()
}
}

0 comments on commit bcebadb

Please sign in to comment.