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
9 changes: 1 addition & 8 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,10 @@ jobs:
- name: Build the binary
run: just build

- name: Setup tests
run: just test-setup
env:
CODE_PATH: /home/runner/code

- name: Run tests
run: just test
env:
CODE_PATH: /home/runner/code

- name: Report test coverage to DeepSource
run: |
curl https://deepsource.io/cli | sh
curl -fsSL https://cli.deepsource.com/install | BINDIR=./bin sh
./bin/deepsource report --analyzer test-coverage --key go --value-file ./coverage.out --use-oidc
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.0.37
2.0.40
8 changes: 2 additions & 6 deletions buildinfo/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ var buildInfo *BuildInfo

// App identity variables. Defaults are prod values; overridden in main.go for dev builds.
var (
AppName = "deepsource" // binary name / display name
ConfigDirName = ".deepsource" // ~/<this>/
KeychainSvc = "deepsource-cli" // macOS keychain service
KeychainKey = "deepsource-cli-token" // macOS keychain account
AppName = "deepsource" // binary name / display name
ConfigDirName = ".deepsource" // ~/<this>/
)

// BuildInfo describes the compile time information.
Expand All @@ -25,7 +23,6 @@ type BuildInfo struct {
BuildMode string `json:"build_mode,omitempty"`
}

// SetBuildInfo sets the build info as a package global.
func SetBuildInfo(version, dateStr, buildMode string) {
date, _ := time.Parse("2006-01-02T15:04:05Z", dateStr)

Expand All @@ -36,7 +33,6 @@ func SetBuildInfo(version, dateStr, buildMode string) {
}
}

// GetBuildInfo returns the package global `buildInfo`
func GetBuildInfo() *BuildInfo {
return buildInfo
}
Expand Down
13 changes: 2 additions & 11 deletions cmd/deepsource/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,9 @@ import (
)

var (
// Version is the build version. This is set using ldflags -X
version = "development"

// Date is the build date. This is set using ldflags -X
Date = "YYYY-MM-DD" // YYYY-MM-DD

// DSN used for sentry
version = "development"
Date = "YYYY-MM-DD"
SentryDSN string

// buildMode is "dev" or "prod" (default). Set via ldflags -X.
buildMode string
)

Expand All @@ -47,8 +40,6 @@ func mainRun() (exitCode int) {
if buildMode == "dev" {
v.AppName = "deepsource-dev"
v.ConfigDirName = ".deepsource-dev"
v.KeychainSvc = "deepsource-dev-cli"
v.KeychainKey = "deepsource-dev-cli-token"
}

// Init sentry
Expand Down
2 changes: 0 additions & 2 deletions command/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ import (
"github.com/deepsourcelabs/cli/command/auth/status"
)

// Options holds the metadata.
type Options struct{}

// NewCmdAuth handles the auth command which has various sub-commands like `login`, `logout` and `status`
func NewCmdAuth() *cobra.Command {
cmd := &cobra.Command{
Use: "auth",
Expand Down
53 changes: 17 additions & 36 deletions command/auth/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (

var accountTypes = []string{"DeepSource (deepsource.com)", "Enterprise Server"}

// LoginOptions hold the metadata related to login operation
type LoginOptions struct {
AuthTimedOut bool
TokenExpired bool
Expand All @@ -28,12 +27,10 @@ type LoginOptions struct {
deps *cmddeps.Deps
}

// NewCmdLogin handles the login functionality for the CLI
func NewCmdLogin() *cobra.Command {
return NewCmdLoginWithDeps(nil)
}

// NewCmdLoginWithDeps creates the login command with injectable dependencies.
func NewCmdLoginWithDeps(deps *cmddeps.Deps) *cobra.Command {
doc := heredoc.Docf(`
Log in to DeepSource using the CLI.
Expand Down Expand Up @@ -66,7 +63,6 @@ func NewCmdLoginWithDeps(deps *cmddeps.Deps) *cobra.Command {
},
}

// --host flag (--hostname kept as deprecated alias)
cmd.Flags().StringVar(&opts.HostName, "host", "", "Authenticate with a specific DeepSource instance")
cmd.Flags().StringVar(&opts.HostName, "hostname", "", "Authenticate with a specific DeepSource instance")
_ = cmd.Flags().MarkDeprecated("hostname", "use --host instead")
Expand All @@ -76,7 +72,6 @@ func NewCmdLoginWithDeps(deps *cmddeps.Deps) *cobra.Command {
return cmd
}

// Run executes the auth command and starts the login flow if not already authenticated
func (opts *LoginOptions) Run() (err error) {
var cfgMgr *config.Manager
if opts.deps != nil && opts.deps.ConfigMgr != nil {
Expand All @@ -92,92 +87,78 @@ func (opts *LoginOptions) Run() (err error) {
} else {
svc = authsvc.NewService(cfgMgr)
}
// Fetch config (errors are non-fatal: a zero config just means "not logged in")
cfg, err := svc.LoadConfig()
if err != nil {
cfg = &config.CLIConfig{}
cfg = config.NewDefault()
}
opts.User = cfg.User
opts.TokenExpired = cfg.IsExpired()

// If local says valid, verify against the server
opts.verifyTokenWithServer(cfg, cfgMgr)
opts.verifyTokenWithServer(cfg, svc)

// Login using the interactive mode
if opts.Interactive {
err = opts.handleInteractiveLogin()
if err != nil {
return err
}
}

// Checking if the user passed a hostname. If yes, storing it in the config
// Else using the default hostname (deepsource.com)
if opts.HostName != "" {
cfg.Host = opts.HostName
} else {
cfg.Host = config.DefaultHostName
}

// If PAT is passed, start the login flow through PAT (skip interactive prompts)
if opts.PAT != "" {
return opts.startPATLoginFlow(svc, cfg, opts.PAT)
}

// Before starting the login workflow, check here for two conditions:
// Condition 1 : If the token has expired, display a message about it and re-authenticate user
// Condition 2 : If the token has not expired,does the user want to re-authenticate?

// Checking for condition 1
if !opts.TokenExpired {
// The user is already logged in, confirm re-authentication.
msg := fmt.Sprintf("You're already logged into DeepSource as %s. Do you want to re-authenticate?", opts.User)
var msg string
if opts.User != "" {
msg = fmt.Sprintf("You're already logged into DeepSource as %s. Do you want to re-authenticate?", opts.User)
} else {
msg = "You're already logged into DeepSource. Do you want to re-authenticate?"
}
response, err := prompt.ConfirmFromUser(msg, "")
if err != nil {
return fmt.Errorf("Error in fetching response. Please try again.")
}
// If the response is No, it implies that the user doesn't want to re-authenticate
// In this case, just exit
if !response {
return nil
}
}

// Condition 2
// `startLoginFlow` implements the authentication flow for the CLI
return opts.startLoginFlow(svc, cfg)
}

func (opts *LoginOptions) verifyTokenWithServer(cfg *config.CLIConfig, cfgMgr *config.Manager) {
if opts.TokenExpired || cfg.Token == "" || cfg.Host == "" {
func (opts *LoginOptions) verifyTokenWithServer(cfg *config.CLIConfig, svc *authsvc.Service) {
if opts.TokenExpired || cfg.Token == "" {
return
}
client, err := deepsource.New(deepsource.ClientOpts{
Token: cfg.Token,
HostName: cfg.Host,
OnTokenRefreshed: cfgMgr.TokenRefreshCallback(),
})
viewer, err := svc.GetViewer(context.Background(), cfg)
if err != nil {
opts.TokenExpired = true
return
}
if _, err := client.GetViewer(context.Background()); err != nil {
opts.TokenExpired = true
// Backfill user from the server when config file was missing or incomplete.
if cfg.User == "" && viewer.Email != "" {
cfg.User = viewer.Email
opts.User = viewer.Email
_ = svc.SaveConfig(cfg)
}
}

func (opts *LoginOptions) handleInteractiveLogin() error {
// Prompt messages and help texts
loginPromptMessage := "Which account do you want to login into?"
loginPromptHelpText := "Select the type of account you want to authenticate"
hostPromptMessage := "Please enter the hostname:"
hostPromptHelpText := "The hostname of the DeepSource instance to authenticate with"

// Display prompt to user
loginType, err := prompt.SelectFromOptions(loginPromptMessage, loginPromptHelpText, accountTypes)
if err != nil {
return err
}
// Prompt the user for hostname only in the case of on-premise
if loginType == "Enterprise Server" {
opts.HostName, err = prompt.GetSingleLineInput(hostPromptMessage, hostPromptHelpText)
if err != nil {
Expand Down
10 changes: 0 additions & 10 deletions command/auth/login/login_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,13 @@ import (
"github.com/fatih/color"
)

// Starts the login flow for the CLI
func (opts *LoginOptions) startLoginFlow(svc *authsvc.Service, cfg *config.CLIConfig) error {
// Register the device and get a device code through the response
ctx := context.Background()
deviceRegistrationResponse, err := registerDevice(ctx, svc, cfg)
if err != nil {
return err
}

// Open the browser for authentication
err = browser.OpenURL(deviceRegistrationResponse.VerificationURIComplete)
if err != nil {
c := color.New(color.FgCyan, color.Bold)
Expand All @@ -38,7 +35,6 @@ func (opts *LoginOptions) startLoginFlow(svc *authsvc.Service, cfg *config.CLICo
fmt.Println()
fmt.Println("Waiting for authentication")

// Fetch the PAT by polling the server
var tokenData *auth.PAT
tokenData, opts.AuthTimedOut, err = fetchPAT(ctx, deviceRegistrationResponse, svc, cfg)
if err != nil {
Expand All @@ -49,7 +45,6 @@ func (opts *LoginOptions) startLoginFlow(svc *authsvc.Service, cfg *config.CLICo
return clierrors.ErrAuthTimeout()
}

// Store auth data in config
cfg.User = tokenData.User.Email
cfg.Token = tokenData.Token
cfg.SetTokenExpiry(tokenData.Expiry)
Expand All @@ -76,9 +71,6 @@ func fetchPAT(ctx context.Context, deviceRegistrationData *auth.Device, svc *aut
userName := ""
authTimedOut := true

/* ======================================================================= */
// The username and hostname to add in the description for the PAT request
/* ======================================================================= */
userData, err := user.Current()
if err != nil {
userName = defaultUserName
Expand All @@ -92,11 +84,9 @@ func fetchPAT(ctx context.Context, deviceRegistrationData *auth.Device, svc *aut
}
userDescription := fmt.Sprintf("CLI PAT for %s@%s", userName, hostName)

// Keep polling the mutation at a certain interval till the expiry timeperiod
ticker := time.NewTicker(time.Duration(deviceRegistrationData.Interval) * time.Second)
pollStartTime := time.Now()

// Polling for fetching PAT
func() {
for range ticker.C {
tokenData, err = svc.RequestPAT(ctx, cfg, deviceRegistrationData.Code, userDescription)
Expand Down
4 changes: 0 additions & 4 deletions command/auth/login/pat_login_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,15 @@ import (
"github.com/fatih/color"
)

// Starts the login flow for the CLI (using PAT)
func (opts *LoginOptions) startPATLoginFlow(svc *authsvc.Service, cfg *config.CLIConfig, token string) error {
// set personal access token (PAT)
cfg.Token = token

// Verify the token against the server before saving
viewer, err := svc.GetViewer(context.Background(), cfg)
if err != nil {
return fmt.Errorf("Invalid token: could not authenticate with DeepSource")
}
cfg.User = viewer.Email

// Having stored the data in the global Cfg object, write it into the config file present in the local filesystem
err = svc.SaveConfig(cfg)
if err != nil {
return fmt.Errorf("Error in writing authentication data to a file. Exiting..")
Expand Down
Loading
Loading