github.com/jasonkeene/cli@v6.14.1-0.20160816203908-ca5715166dfb+incompatible/cf/commands/application/ssh.go (about) 1 package application 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "time" 8 9 "golang.org/x/crypto/ssh" 10 11 "github.com/cloudfoundry/cli/cf/commandregistry" 12 "github.com/cloudfoundry/cli/cf/commands" 13 "github.com/cloudfoundry/cli/cf/configuration/coreconfig" 14 "github.com/cloudfoundry/cli/cf/flags" 15 . "github.com/cloudfoundry/cli/cf/i18n" 16 "github.com/cloudfoundry/cli/cf/net" 17 "github.com/cloudfoundry/cli/cf/requirements" 18 sshCmd "github.com/cloudfoundry/cli/cf/ssh" 19 "github.com/cloudfoundry/cli/cf/ssh/options" 20 sshTerminal "github.com/cloudfoundry/cli/cf/ssh/terminal" 21 "github.com/cloudfoundry/cli/cf/terminal" 22 ) 23 24 type SSH struct { 25 ui terminal.UI 26 config coreconfig.Reader 27 gateway net.Gateway 28 appReq requirements.ApplicationRequirement 29 sshCodeGetter commands.SSHCodeGetter 30 opts *options.SSHOptions 31 secureShell sshCmd.SecureShell 32 } 33 34 type sshInfo struct { 35 SSHEndpoint string `json:"app_ssh_endpoint"` 36 SSHEndpointFingerprint string `json:"app_ssh_host_key_fingerprint"` 37 } 38 39 func init() { 40 commandregistry.Register(&SSH{}) 41 } 42 43 func (cmd *SSH) MetaData() commandregistry.CommandMetadata { 44 fs := make(map[string]flags.FlagSet) 45 fs["L"] = &flags.StringSliceFlag{ShortName: "L", Usage: T("Local port forward specification. This flag can be defined more than once.")} 46 fs["command"] = &flags.StringSliceFlag{Name: "command", ShortName: "c", Usage: T("Command to run. This flag can be defined more than once.")} 47 fs["app-instance-index"] = &flags.IntFlag{Name: "app-instance-index", ShortName: "i", Usage: T("Application instance index")} 48 fs["skip-host-validation"] = &flags.BoolFlag{Name: "skip-host-validation", ShortName: "k", Usage: T("Skip host key validation")} 49 fs["skip-remote-execution"] = &flags.BoolFlag{Name: "skip-remote-execution", ShortName: "N", Usage: T("Do not execute a remote command")} 50 fs["request-pseudo-tty"] = &flags.BoolFlag{Name: "request-pseudo-tty", ShortName: "t", Usage: T("Request pseudo-tty allocation")} 51 fs["force-pseudo-tty"] = &flags.BoolFlag{Name: "force-pseudo-tty", ShortName: "tt", Usage: T("Force pseudo-tty allocation")} 52 fs["disable-pseudo-tty"] = &flags.BoolFlag{Name: "disable-pseudo-tty", ShortName: "T", Usage: T("Disable pseudo-tty allocation")} 53 54 return commandregistry.CommandMetadata{ 55 Name: "ssh", 56 Description: T("SSH to an application container instance"), 57 Usage: []string{ 58 T("CF_NAME ssh APP_NAME [-i app-instance-index] [-c command] [-L [bind_address:]port:host:hostport] [--skip-host-validation] [--skip-remote-execution] [--request-pseudo-tty] [--force-pseudo-tty] [--disable-pseudo-tty]"), 59 }, 60 Flags: fs, 61 } 62 } 63 64 func (cmd *SSH) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) ([]requirements.Requirement, error) { 65 if len(fc.Args()) != 1 { 66 cmd.ui.Failed(T("Incorrect Usage. Requires APP_NAME as argument") + "\n\n" + commandregistry.Commands.CommandUsage("ssh")) 67 return nil, fmt.Errorf("Incorrect usage: %d arguments of %d required", len(fc.Args()), 1) 68 } 69 70 if fc.IsSet("i") && fc.Int("i") < 0 { 71 cmd.ui.Failed(fmt.Sprintf(T("Incorrect Usage:")+" %s\n\n%s", T("Value for flag 'app-instance-index' cannot be negative"), commandregistry.Commands.CommandUsage("ssh"))) 72 return nil, fmt.Errorf("Incorrect usage: app-instance-index cannot be negative") 73 } 74 75 var err error 76 cmd.opts, err = options.NewSSHOptions(fc) 77 78 if err != nil { 79 cmd.ui.Failed(fmt.Sprintf(T("Incorrect Usage:")+" %s\n\n%s", err.Error(), commandregistry.Commands.CommandUsage("ssh"))) 80 return nil, err 81 } 82 83 cmd.appReq = requirementsFactory.NewApplicationRequirement(cmd.opts.AppName) 84 85 reqs := []requirements.Requirement{ 86 requirementsFactory.NewLoginRequirement(), 87 requirementsFactory.NewTargetedSpaceRequirement(), 88 cmd.appReq, 89 } 90 91 return reqs, nil 92 } 93 94 func (cmd *SSH) SetDependency(deps commandregistry.Dependency, pluginCall bool) commandregistry.Command { 95 cmd.ui = deps.UI 96 cmd.config = deps.Config 97 cmd.gateway = deps.Gateways["cloud-controller"] 98 99 if deps.WildcardDependency != nil { 100 cmd.secureShell = deps.WildcardDependency.(sshCmd.SecureShell) 101 } 102 103 //get ssh-code for dependency 104 sshCodeGetter := commandregistry.Commands.FindCommand("ssh-code") 105 sshCodeGetter = sshCodeGetter.SetDependency(deps, false) 106 cmd.sshCodeGetter = sshCodeGetter.(commands.SSHCodeGetter) 107 108 return cmd 109 } 110 111 func (cmd *SSH) Execute(fc flags.FlagContext) error { 112 app := cmd.appReq.GetApplication() 113 info, err := cmd.getSSHEndpointInfo() 114 if err != nil { 115 return errors.New(T("Error getting SSH info:") + err.Error()) 116 } 117 118 sshAuthCode, err := cmd.sshCodeGetter.Get() 119 if err != nil { 120 return errors.New(T("Error getting one time auth code: ") + err.Error()) 121 } 122 123 //init secureShell if it is not already set by SetDependency() with fakes 124 if cmd.secureShell == nil { 125 cmd.secureShell = sshCmd.NewSecureShell( 126 sshCmd.DefaultSecureDialer(), 127 sshTerminal.DefaultHelper(), 128 sshCmd.DefaultListenerFactory(), 129 30*time.Second, 130 app, 131 info.SSHEndpointFingerprint, 132 info.SSHEndpoint, 133 sshAuthCode, 134 ) 135 } 136 137 err = cmd.secureShell.Connect(cmd.opts) 138 if err != nil { 139 return errors.New(T("Error opening SSH connection: ") + err.Error()) 140 } 141 defer cmd.secureShell.Close() 142 143 err = cmd.secureShell.LocalPortForward() 144 if err != nil { 145 return errors.New(T("Error forwarding port: ") + err.Error()) 146 } 147 148 if cmd.opts.SkipRemoteExecution { 149 err = cmd.secureShell.Wait() 150 } else { 151 err = cmd.secureShell.InteractiveSession() 152 } 153 154 if err != nil { 155 if exitError, ok := err.(*ssh.ExitError); ok { 156 exitStatus := exitError.ExitStatus() 157 if sig := exitError.Signal(); sig != "" { 158 cmd.ui.Say(fmt.Sprintf(T("Process terminated by signal: %s. Exited with")+" %d.\n", sig, exitStatus)) 159 } 160 os.Exit(exitStatus) 161 } else { 162 return errors.New(T("Error: ") + err.Error()) 163 } 164 } 165 return nil 166 } 167 168 func (cmd *SSH) getSSHEndpointInfo() (sshInfo, error) { 169 info := sshInfo{} 170 err := cmd.gateway.GetResource(cmd.config.APIEndpoint()+"/v2/info", &info) 171 return info, err 172 }