Skip to content
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
16 changes: 5 additions & 11 deletions cmd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
"golang.org/x/term"
)

type apiOptions struct {
type APIOptions struct {
method string
requestBody string
inputFile string
Expand All @@ -26,12 +26,12 @@ type apiOptions struct {
noColor bool
}

func newApiCmd() *cobra.Command {
opts := apiOptions{}
func NewCmdAPI() *cobra.Command {
opts := APIOptions{}

cmd := &cobra.Command{
Use: "api <endpoint>",
Short: "Make authenticated requests to the Glean API",
Use: "api",
Short: "Make an authenticated HTTP request to the Glean API",
Long: heredoc.Doc(`
Makes an authenticated HTTP request to the Glean API and prints the response.

Expand Down Expand Up @@ -155,10 +155,6 @@ func newApiCmd() *cobra.Command {
return cmd
}

func init() {
rootCmd.AddCommand(newApiCmd())
}

func previewRequest(req *http.Request, noColor bool) error {
cfg, err := config.LoadConfig()
if err != nil {
Expand Down Expand Up @@ -200,15 +196,13 @@ func previewRequest(req *http.Request, noColor bool) error {
return nil
}

// maskToken masks most of the token characters for display
func maskToken(token string) string {
if len(token) <= 8 {
return strings.Repeat("*", len(token))
}
return token[:4] + strings.Repeat("*", len(token)-8) + token[len(token)-4:]
}

// isatty returns true if the given file descriptor is a terminal
func isatty(fd uintptr) bool {
return term.IsTerminal(int(fd))
}
51 changes: 10 additions & 41 deletions cmd/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,42 @@ package cmd

import (
"bytes"
"fmt"
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)

func TestApiCmd(t *testing.T) {
tests := []struct {
name string
wantOutput string
args []string
wantErr bool
name string
args []string
wantErr bool
}{
{
name: "default GET method",
args: []string{"/search"},
wantOutput: "Invoking Glean API with method=GET, endpoint=/search",
name: "no endpoint provided",
args: []string{},
wantErr: true,
},
{
name: "POST method",
args: []string{"--method", "POST", "/users"},
wantOutput: "Invoking Glean API with method=POST, endpoint=/users",
},
{
name: "custom method with -X flag",
args: []string{"-X", "PUT", "/update"},
wantOutput: "Invoking Glean API with method=PUT, endpoint=/update",
},
{
name: "no endpoint provided",
args: []string{},
wantOutput: "Invoking Glean API with method=GET, endpoint=",
name: "help flag",
args: []string{"--help"},
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a fresh command for each test
cmd := &cobra.Command{
Use: "api",
Short: "Make calls to the Glean API",
RunE: func(cmd *cobra.Command, args []string) error {
method, _ := cmd.Flags().GetString("method")
endpoint := ""
if len(args) > 0 {
endpoint = args[0]
}
fmt.Fprintf(cmd.OutOrStdout(), "Invoking Glean API with method=%s, endpoint=%s\n", method, endpoint)
return nil
},
}
cmd.Flags().StringP("method", "X", "GET", "HTTP method to use (GET, POST, etc.)")

b := bytes.NewBufferString("")
cmd := NewCmdAPI()
cmd.SetOut(b)
cmd.SetErr(b)
cmd.SetArgs(tt.args)

err := cmd.Execute()

if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
output := b.String()
assert.Contains(t, output, tt.wantOutput)
}
})
}
Expand Down
136 changes: 69 additions & 67 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,78 +9,90 @@ import (
"github.com/spf13/cobra"
)

var (
const notSetValue = "[not set]"

type ConfigOptions struct {
host string
token string
email string
clear bool
show bool
)

const notSetValue = "[not set]"
}

var configCmd = &cobra.Command{
Use: "config",
Short: "Configure Glean CLI credentials",
Long: heredoc.Doc(`
Configure credentials for the Glean CLI.

Examples:
# Set Glean host (either format works)
glean config --host linkedin
glean config --host linkedin-be.glean.com

# Set Glean API token
glean config --token your-token

# Set Glean user email
glean config --email user@company.com

# Show current configuration
glean config --show

# Clear all stored credentials
glean config --clear
`),
RunE: func(cmd *cobra.Command, args []string) error {
if show {
cfg, err := config.LoadConfig()
if err != nil {
return fmt.Errorf("failed to access keyring: %w", err)
func NewCmdConfig() *cobra.Command {
opts := ConfigOptions{}

cmd := &cobra.Command{
Use: "config",
Short: "Manage Glean CLI configuration",
Long: heredoc.Doc(`
Configure credentials for the Glean CLI.

Examples:
# Set Glean host (either format works)
glean config --host linkedin
glean config --host linkedin-be.glean.com

# Set Glean API token
glean config --token your-token

# Set Glean user email
glean config --email user@company.com

# Show current configuration
glean config --show

# Clear all stored credentials
glean config --clear
`),
RunE: func(cmd *cobra.Command, args []string) error {
if opts.show {
cfg, err := config.LoadConfig()
if err != nil {
return fmt.Errorf("failed to access keyring: %w", err)
}

fmt.Println("Current configuration:")
fmt.Printf(" %-10s %s\n", "Host:", valueOrNotSet(cfg.GleanHost))
fmt.Printf(" %-10s %s\n", "Email:", valueOrNotSet(cfg.GleanEmail))

// Mask token if present
tokenDisplay := notSetValue
if cfg.GleanToken != "" {
tokenDisplay = cfg.GleanToken[0:4] + strings.Repeat("*", len(cfg.GleanToken)-4)
}
fmt.Printf(" %-10s %s\n", "Token:", tokenDisplay)
return nil
}

fmt.Println("Current configuration:")
fmt.Printf(" %-10s %s\n", "Host:", valueOrNotSet(cfg.GleanHost))
fmt.Printf(" %-10s %s\n", "Email:", valueOrNotSet(cfg.GleanEmail))
if opts.clear {
if err := config.ClearConfig(); err != nil {
return fmt.Errorf("failed to clear configuration: %w", err)
}
fmt.Println("Configuration cleared successfully")
return nil
}

// Mask token if present
tokenDisplay := notSetValue
if cfg.GleanToken != "" {
tokenDisplay = cfg.GleanToken[0:4] + strings.Repeat("*", len(cfg.GleanToken)-4)
if opts.host == "" && opts.token == "" && opts.email == "" {
return fmt.Errorf("no configuration provided. Use --host, --token, or --email to set configuration")
}
fmt.Printf(" %-10s %s\n", "Token:", tokenDisplay)
return nil
}

if clear {
if err := config.ClearConfig(); err != nil {
return fmt.Errorf("failed to clear configuration: %w", err)
if err := config.SaveConfig(opts.host, opts.token, opts.email); err != nil {
return fmt.Errorf("failed to save configuration: %w", err)
}
fmt.Println("Configuration cleared successfully")
return nil
}

if host == "" && token == "" && email == "" {
return fmt.Errorf("no configuration provided. Use --host, --token, or --email to set configuration")
}
fmt.Println("Configuration saved successfully")
return nil
},
}

if err := config.SaveConfig(host, token, email); err != nil {
return fmt.Errorf("failed to save configuration: %w", err)
}
cmd.Flags().StringVar(&opts.host, "host", "", "Glean instance name or full hostname (e.g., 'linkedin' or 'linkedin-be.glean.com')")
cmd.Flags().StringVar(&opts.token, "token", "", "Glean API token")
cmd.Flags().StringVar(&opts.email, "email", "", "Email address for API requests")
cmd.Flags().BoolVar(&opts.clear, "clear", false, "Clear all stored credentials")
cmd.Flags().BoolVar(&opts.show, "show", false, "Show current configuration")

fmt.Println("Configuration saved successfully")
return nil
},
return cmd
}

func valueOrNotSet(value string) string {
Expand All @@ -89,13 +101,3 @@ func valueOrNotSet(value string) string {
}
return value
}

func init() {
rootCmd.AddCommand(configCmd)

configCmd.Flags().StringVar(&host, "host", "", "Glean instance name or full hostname (e.g., 'linkedin' or 'linkedin-be.glean.com')")
configCmd.Flags().StringVar(&token, "token", "", "Glean API token")
configCmd.Flags().StringVar(&email, "email", "", "Email address for API requests")
configCmd.Flags().BoolVar(&clear, "clear", false, "Clear all stored credentials")
configCmd.Flags().BoolVar(&show, "show", false, "Show current configuration")
}
Loading
Loading