github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/synchronization/protocols/ssh/protocol.go (about) 1 package ssh 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 8 "github.com/mutagen-io/mutagen/pkg/agent" 9 "github.com/mutagen-io/mutagen/pkg/agent/transport/ssh" 10 "github.com/mutagen-io/mutagen/pkg/logging" 11 "github.com/mutagen-io/mutagen/pkg/synchronization" 12 "github.com/mutagen-io/mutagen/pkg/synchronization/endpoint/remote" 13 urlpkg "github.com/mutagen-io/mutagen/pkg/url" 14 ) 15 16 // protocolHandler implements the synchronization.ProtocolHandler interface for 17 // connecting to remote endpoints over SSH. It uses the agent infrastructure 18 // over an SSH transport. 19 type protocolHandler struct{} 20 21 // dialResult provides asynchronous agent dialing results. 22 type dialResult struct { 23 // stream is the stream returned by agent dialing. 24 stream io.ReadWriteCloser 25 // error is the error returned by agent dialing. 26 error error 27 } 28 29 // Connect connects to an SSH endpoint. 30 func (h *protocolHandler) Connect( 31 ctx context.Context, 32 logger *logging.Logger, 33 url *urlpkg.URL, 34 prompter string, 35 session string, 36 version synchronization.Version, 37 configuration *synchronization.Configuration, 38 alpha bool, 39 ) (synchronization.Endpoint, error) { 40 // Verify that the URL is of the correct kind and protocol. 41 if url.Kind != urlpkg.Kind_Synchronization { 42 panic("non-synchronization URL dispatched to synchronization protocol handler") 43 } else if url.Protocol != urlpkg.Protocol_SSH { 44 panic("non-SSH URL dispatched to SSH protocol handler") 45 } 46 47 // Create an SSH agent transport. 48 transport, err := ssh.NewTransport(url.User, url.Host, uint16(url.Port), prompter) 49 if err != nil { 50 return nil, fmt.Errorf("unable to create SSH transport: %w", err) 51 } 52 53 // Create a channel to deliver the dialing result. 54 results := make(chan dialResult) 55 56 // Perform dialing in a background Goroutine so that we can monitor for 57 // cancellation. 58 go func() { 59 // Perform the dialing operation. 60 stream, err := agent.Dial(logger, transport, agent.CommandSynchronizer, prompter) 61 62 // Transmit the result or, if cancelled, close the stream. 63 select { 64 case results <- dialResult{stream, err}: 65 case <-ctx.Done(): 66 if stream != nil { 67 stream.Close() 68 } 69 } 70 }() 71 72 // Wait for dialing results or cancellation. 73 var stream io.ReadWriteCloser 74 select { 75 case result := <-results: 76 if result.error != nil { 77 return nil, fmt.Errorf("unable to dial agent endpoint: %w", result.error) 78 } 79 stream = result.stream 80 case <-ctx.Done(): 81 return nil, context.Canceled 82 } 83 84 // Create the endpoint client. 85 return remote.NewEndpoint(logger, stream, url.Path, session, version, configuration, alpha) 86 } 87 88 func init() { 89 // Register the SSH protocol handler with the synchronization package. 90 synchronization.ProtocolHandlers[urlpkg.Protocol_SSH] = &protocolHandler{} 91 }