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  }