github.com/swisscom/cloudfoundry-cli@v7.1.0+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 "code.cloudfoundry.org/cli/cf/commandregistry" 12 "code.cloudfoundry.org/cli/cf/commands" 13 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 14 "code.cloudfoundry.org/cli/cf/flags" 15 . "code.cloudfoundry.org/cli/cf/i18n" 16 "code.cloudfoundry.org/cli/cf/net" 17 "code.cloudfoundry.org/cli/cf/requirements" 18 sshCmd "code.cloudfoundry.org/cli/cf/ssh" 19 "code.cloudfoundry.org/cli/cf/ssh/options" 20 sshTerminal "code.cloudfoundry.org/cli/cf/ssh/terminal" 21 "code.cloudfoundry.org/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 var err error 71 cmd.opts, err = options.NewSSHOptions(fc) 72 73 if err != nil { 74 cmd.ui.Failed(fmt.Sprintf(T("Incorrect Usage:")+" %s\n\n%s", err.Error(), commandregistry.Commands.CommandUsage("ssh"))) 75 return nil, err 76 } 77 78 cmd.appReq = requirementsFactory.NewApplicationRequirement(cmd.opts.AppName) 79 80 reqs := []requirements.Requirement{ 81 requirementsFactory.NewLoginRequirement(), 82 requirementsFactory.NewTargetedSpaceRequirement(), 83 cmd.appReq, 84 } 85 86 return reqs, nil 87 } 88 89 func (cmd *SSH) SetDependency(deps commandregistry.Dependency, pluginCall bool) commandregistry.Command { 90 cmd.ui = deps.UI 91 cmd.config = deps.Config 92 cmd.gateway = deps.Gateways["cloud-controller"] 93 94 if deps.WildcardDependency != nil { 95 cmd.secureShell = deps.WildcardDependency.(sshCmd.SecureShell) 96 } 97 98 //get ssh-code for dependency 99 sshCodeGetter := commandregistry.Commands.FindCommand("ssh-code") 100 sshCodeGetter = sshCodeGetter.SetDependency(deps, false) 101 cmd.sshCodeGetter = sshCodeGetter.(commands.SSHCodeGetter) 102 103 return cmd 104 } 105 106 func (cmd *SSH) Execute(fc flags.FlagContext) error { 107 if fc.IsSet("i") { 108 instanceIndex := fc.Int("i") 109 if instanceIndex < 0 { 110 return fmt.Errorf(T("The application instance index cannot be negative")) 111 } 112 if instanceIndex >= cmd.appReq.GetApplication().InstanceCount { 113 return fmt.Errorf(T("The specified application instance does not exist")) 114 } 115 } 116 117 app := cmd.appReq.GetApplication() 118 info, err := cmd.getSSHEndpointInfo() 119 if err != nil { 120 return errors.New(T("Error getting SSH info:") + err.Error()) 121 } 122 123 sshAuthCode, err := cmd.sshCodeGetter.Get() 124 if err != nil { 125 return errors.New(T("Error getting one time auth code: ") + err.Error()) 126 } 127 128 //init secureShell if it is not already set by SetDependency() with fakes 129 if cmd.secureShell == nil { 130 cmd.secureShell = sshCmd.NewSecureShell( 131 sshCmd.DefaultSecureDialer(), 132 sshTerminal.DefaultHelper(), 133 sshCmd.DefaultListenerFactory(), 134 30*time.Second, 135 app, 136 info.SSHEndpointFingerprint, 137 info.SSHEndpoint, 138 sshAuthCode, 139 ) 140 } 141 142 err = cmd.secureShell.Connect(cmd.opts) 143 if err != nil { 144 return errors.New(T("Error opening SSH connection: ") + err.Error()) 145 } 146 defer cmd.secureShell.Close() 147 148 err = cmd.secureShell.LocalPortForward() 149 if err != nil { 150 return errors.New(T("Error forwarding port: ") + err.Error()) 151 } 152 153 if cmd.opts.SkipRemoteExecution { 154 err = cmd.secureShell.Wait() 155 } else { 156 err = cmd.secureShell.InteractiveSession() 157 } 158 159 if err != nil { 160 if exitError, ok := err.(*ssh.ExitError); ok { 161 exitStatus := exitError.ExitStatus() 162 if sig := exitError.Signal(); sig != "" { 163 cmd.ui.Say(T("Process terminated by signal: {{.Signal}}. Exited with {{.ExitCode}}", map[string]interface{}{ 164 "Signal": sig, 165 "ExitCode": exitStatus, 166 })) 167 } 168 os.Exit(exitStatus) 169 } else { 170 return errors.New(T("Error: ") + err.Error()) 171 } 172 } 173 return nil 174 } 175 176 func (cmd *SSH) getSSHEndpointInfo() (sshInfo, error) { 177 info := sshInfo{} 178 err := cmd.gateway.GetResource(cmd.config.APIEndpoint()+"/v2/info", &info) 179 return info, err 180 }