Skip to content

Commit

Permalink
Merge pull request #263 from Scalingo/fix/262
Browse files Browse the repository at this point in the history
Fixes #262 Automatically try SSH with ssh-agent if available
  • Loading branch information
Soulou committed Nov 16, 2016
2 parents e5e7fb9 + b3a9cc7 commit bd34c36
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 49 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
Changelog
=========

### v1.4.0 -
### v1.4.0 - 16/11/2016

* [Feature] Add timeline and user-timeline to display per are of user-global activities #235
* [Feature] Add list, remove and add commands for notifications
* [Feature] Add `deployments` command to get the a deployments list for an application #222 #234
* [Feature] Add `deployment-logs` command to get logs for a specific deployment
* [Feature] Add `deployment-follow` command to follow the deployment stream for an application
* [Feature - Login] Automatically try SSH with ssh-agent if available #262
* [Feature - Create] --buildpack flag to specify a custom buildpack
* [Fix] Fix error handling when an addon fails to get provisioned #252
* [Fix] Fix error display when an application doesn't have any log available #249
Expand Down
15 changes: 10 additions & 5 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions cmd/authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
)

func AuthenticateHook(c *cli.Context) error {
if config.AuthenticatedUser == nil {
return session.Login(session.LoginOpts{SshIdentity: "ssh-agent"})
if config.AuthenticatedUser != nil {
return nil
}
return nil
return session.Login(session.LoginOpts{})
}
2 changes: 1 addition & 1 deletion cmd/login.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package cmd

import (
"github.com/Scalingo/codegangsta-cli"
"github.com/Scalingo/cli/cmd/autocomplete"
"github.com/Scalingo/cli/session"
"github.com/Scalingo/codegangsta-cli"
)

var (
Expand Down
7 changes: 3 additions & 4 deletions cmd/logout.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package cmd

import (
"fmt"

"github.com/Scalingo/cli/cmd/autocomplete"
"github.com/Scalingo/cli/config"
"github.com/Scalingo/cli/io"
"github.com/Scalingo/cli/session"
"github.com/Scalingo/codegangsta-cli"
)
Expand All @@ -17,13 +16,13 @@ var (
Description: "Destroy login information stored on your computer",
Action: func(c *cli.Context) {
if config.AuthenticatedUser == nil {
fmt.Println("You are already logged out.")
io.Status("You are already logged out.")
return
}
if err := session.DestroyToken(); err != nil {
panic(err)
}
fmt.Println("Scalingo credentials have been deleted.")
io.Status("Scalingo credentials have been deleted.")
},
BashComplete: func(c *cli.Context) {
autocomplete.CmdFlagsAutoComplete(c, "logout")
Expand Down
39 changes: 30 additions & 9 deletions config/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package config

import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
Expand All @@ -12,11 +11,17 @@ import (

"github.com/Scalingo/cli/config/auth"
"github.com/Scalingo/cli/debug"
appio "github.com/Scalingo/cli/io"
"github.com/Scalingo/cli/term"
"github.com/Scalingo/go-scalingo"
"github.com/pkg/errors"
"gopkg.in/errgo.v1"
)

var (
ErrAuthenticationFailed = errors.New("authentication failed")
)

type CliAuthenticator struct{}

var (
Expand All @@ -28,7 +33,6 @@ func Auth() (*scalingo.User, error) {
var user *scalingo.User
var err error

fmt.Fprintln(os.Stderr, "You need to be authenticated to use Scalingo client.\nNo account ? → https://scalingo.com")
if C.DisableInteractive {
err = errors.New("Fail to login (interactive mode disabled)")
} else {
Expand All @@ -39,25 +43,38 @@ func Auth() (*scalingo.User, error) {
} else if errgo.Cause(err) == io.EOF {
return nil, errors.New("canceled by user")
} else {
fmt.Printf("Fail to login (%d/3): %v\n", i+1, err)

appio.Errorf("Fail to login (%d/3): %v\n\n", i+1, err)
}
}
}
if err == ErrAuthenticationFailed {
return nil, errors.New("Forgot your password? https://my.scalingo.com")
}
if err != nil {
return nil, errgo.Mask(err, errgo.Any)
}

fmt.Printf("Hello %s, nice to see you!\n\n", user.Username)
C.apiToken = user.AuthenticationToken
AuthenticatedUser = user
err = Authenticator.StoreAuth(user)
fmt.Print("\n")
appio.Statusf("Hello %s, nice to see you!\n\n", user.Username)
err = SetCurrentUser(user)
if err != nil {
return nil, errgo.Mask(err, errgo.Any)
}

return user, nil
}

func SetCurrentUser(user *scalingo.User) error {
C.apiToken = user.AuthenticationToken
AuthenticatedUser = user
err := Authenticator.StoreAuth(user)
if err != nil {
return errgo.Mask(err, errgo.Any)
}
return nil
}

func (a *CliAuthenticator) StoreAuth(user *scalingo.User) error {
authConfig, err := existingAuth()
if err != nil {
Expand Down Expand Up @@ -157,7 +174,7 @@ func tryAuth() (*scalingo.User, error) {
var err error

for login == "" {
fmt.Print("Username or email: ")
appio.Infof("Username or email: ")
_, err := fmt.Scanln(&login)
if err != nil {
if strings.Contains(err.Error(), "unexpected newline") {
Expand All @@ -168,16 +185,20 @@ func tryAuth() (*scalingo.User, error) {
login = strings.TrimRight(login, "\n")
}

password, err := term.Password("Password: ")
password, err := term.Password(" Password: ")
if err != nil {
return nil, errgo.Mask(err, errgo.Any)
}
fmt.Print("\n")

c := ScalingoUnauthenticatedClient()
res, err := c.Login(login, password)
if err != nil {
return nil, errgo.Mask(err, errgo.Any)
}
if res.User == nil {
return nil, ErrAuthenticationFailed
}
return res.User, nil
}

Expand Down
4 changes: 4 additions & 0 deletions io/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ func Error(args ...interface{}) {
fmt.Println(args...)
}

func Errorf(format string, args ...interface{}) {
fmt.Printf(" ! "+format, args...)
}

func Warning(args ...interface{}) {
fmt.Print(" /!\\ ")
fmt.Println(args...)
Expand Down
6 changes: 3 additions & 3 deletions scalingo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import (
"os"
"strings"

"github.com/Scalingo/codegangsta-cli"
"github.com/stvp/rollbar"
"github.com/Scalingo/cli/cmd"
"github.com/Scalingo/cli/cmd/autocomplete"
"github.com/Scalingo/cli/config"
"github.com/Scalingo/cli/signals"
"github.com/Scalingo/cli/update"
"github.com/Scalingo/codegangsta-cli"
"github.com/stvp/rollbar"
)

func DefaultAction(c *cli.Context) {
Expand Down Expand Up @@ -98,6 +98,6 @@ func main() {
}

if err := app.Run(os.Args); err != nil {
fmt.Println("Fail to run scalingo", err)
fmt.Println("Fail to run command:", err)
}
}
53 changes: 30 additions & 23 deletions session/login.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package session

import (
"fmt"

"github.com/Scalingo/cli/config"
"github.com/Scalingo/cli/debug"
"github.com/Scalingo/cli/io"
netssh "github.com/Scalingo/cli/net/ssh"
"github.com/pkg/errors"
"gopkg.in/errgo.v1"
)

Expand All @@ -16,31 +16,41 @@ type LoginOpts struct {
}

func Login(opts LoginOpts) error {
if opts.ApiKey != "" && opts.Ssh {
return errgo.New("only use --api-key or --ssh")
}

if opts.SshIdentity != "ssh-agent" && !opts.Ssh {
return errgo.New("you can't use --ssh-identify without having --ssh")
if opts.SshIdentity == "" {
opts.SshIdentity = "ssh-agent"
}

if opts.ApiKey != "" {
return loginWithApiKey(opts.ApiKey)
}

if opts.Ssh {
return loginWithSsh(opts.SshIdentity)
if config.AuthenticatedUser != nil {
io.Statusf("You are already logged as %s (%s)!\n", config.AuthenticatedUser.Email, config.AuthenticatedUser.Username)
return nil
}
io.Status("Currently unauthenticated")
io.Info("Trying login with SSH…")
err := loginWithSsh(opts.SshIdentity)
if err != nil {
config.C.Logger.Printf("SSH connection failed: %+v\n", err)
io.Error("SSH connection failed.")
if opts.Ssh {
if errors.Cause(err) == netssh.ErrNoAuthSucceed {
return errors.Wrap(err, "please use the flag '--ssh-identity /path/to/private/key' to specify your private key")
}
if err != nil {
return errors.Wrap(err, "fail to login wish SSH")
}
} else {
io.Info("Trying login with user/password:\n")
return loginWithUserAndPassword()
}
}

return loginWithUserAndPassword()
return nil
}

func loginWithUserAndPassword() error {
if config.AuthenticatedUser != nil {
fmt.Printf("You are already logged as %s (%s)!\n", config.AuthenticatedUser.Email, config.AuthenticatedUser.Username)
return nil
}

_, err := config.Auth()
if err != nil {
return errgo.Mask(err, errgo.Any)
Expand All @@ -56,10 +66,10 @@ func loginWithApiKey(apiKey string) error {
return errgo.Mask(err)
}

fmt.Printf("Hello %s, nice to see you!\n", user.Username)
user.AuthenticationToken = apiKey
io.Statusf("Hello %s, nice to see you!\n", user.Username)

err = config.Authenticator.StoreAuth(user)
user.AuthenticationToken = apiKey
err = config.SetCurrentUser(user)
if err != nil {
return errgo.Mask(err)
}
Expand All @@ -70,10 +80,7 @@ func loginWithSsh(identity string) error {
debug.Println("Login through SSH, identity:", identity)
client, _, err := netssh.Connect(identity)
if err != nil {
if err == netssh.ErrNoAuthSucceed {
return errgo.Notef(err, "please use the flag '--ssh-identity /path/to/private/key' to specify your private key")
}
return errgo.Notef(err, "fail to connect to SSH server")
return errors.Wrap(err, "fail to connect to SSH server")
}
channel, reqs, err := client.OpenChannel("session", []byte{})
if err != nil {
Expand Down

0 comments on commit bd34c36

Please sign in to comment.