diff --git a/provisioner/ansible/adapter.go b/common/adapter/adapter.go similarity index 93% rename from provisioner/ansible/adapter.go rename to common/adapter/adapter.go index c3dfd3495..510ae40bb 100644 --- a/provisioner/ansible/adapter.go +++ b/common/adapter/adapter.go @@ -1,4 +1,4 @@ -package ansible +package adapter import ( "bytes" @@ -17,7 +17,7 @@ import ( // An adapter satisfies SSH requests (from an Ansible client) by delegating SSH // exec and subsystem commands to a packer.Communicator. -type adapter struct { +type Adapter struct { done <-chan struct{} l net.Listener config *ssh.ServerConfig @@ -26,8 +26,8 @@ type adapter struct { comm packer.Communicator } -func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, sftpCmd string, ui packer.Ui, comm packer.Communicator) *adapter { - return &adapter{ +func NewAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, sftpCmd string, ui packer.Ui, comm packer.Communicator) *Adapter { + return &Adapter{ done: done, l: l, config: config, @@ -37,7 +37,7 @@ func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, } } -func (c *adapter) Serve() { +func (c *Adapter) Serve() { log.Printf("SSH proxy: serving on %s", c.l.Addr()) for { @@ -62,7 +62,7 @@ func (c *adapter) Serve() { } } -func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error { +func (c *Adapter) Handle(conn net.Conn, ui packer.Ui) error { log.Print("SSH proxy: accepted connection") _, chans, reqs, err := ssh.NewServerConn(conn, c.config) if err != nil { @@ -89,7 +89,7 @@ func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error { return nil } -func (c *adapter) handleSession(newChannel ssh.NewChannel) error { +func (c *Adapter) handleSession(newChannel ssh.NewChannel) error { channel, requests, err := newChannel.Accept() if err != nil { return err @@ -182,11 +182,11 @@ func (c *adapter) handleSession(newChannel ssh.NewChannel) error { return nil } -func (c *adapter) Shutdown() { +func (c *Adapter) Shutdown() { c.l.Close() } -func (c *adapter) exec(command string, in io.Reader, out io.Writer, err io.Writer) int { +func (c *Adapter) exec(command string, in io.Reader, out io.Writer, err io.Writer) int { var exitStatus int switch { case strings.HasPrefix(command, "scp ") && serveSCP(command[4:]): @@ -206,7 +206,7 @@ func serveSCP(args string) bool { return bytes.IndexAny(opts, "tf") >= 0 } -func (c *adapter) scpExec(args string, in io.Reader, out io.Writer) error { +func (c *Adapter) scpExec(args string, in io.Reader, out io.Writer) error { opts, rest := scpOptions(args) // remove the quoting that ansible added to rest for shell safety. @@ -226,7 +226,7 @@ func (c *adapter) scpExec(args string, in io.Reader, out io.Writer) error { return errors.New("no scp mode specified") } -func (c *adapter) remoteExec(command string, in io.Reader, out io.Writer, err io.Writer) int { +func (c *Adapter) remoteExec(command string, in io.Reader, out io.Writer, err io.Writer) int { cmd := &packer.RemoteCmd{ Stdin: in, Stdout: out, diff --git a/provisioner/inspec/adapter_test.go b/common/adapter/adapter_test.go similarity index 96% rename from provisioner/inspec/adapter_test.go rename to common/adapter/adapter_test.go index 638cead58..a43b3bedc 100644 --- a/provisioner/inspec/adapter_test.go +++ b/common/adapter/adapter_test.go @@ -1,4 +1,4 @@ -package inspec +package adapter import ( "errors" @@ -26,7 +26,7 @@ func TestAdapter_Serve(t *testing.T) { ui := new(packer.NoopUi) - sut := newAdapter(done, &l, config, newUi(ui), communicator{}) + sut := NewAdapter(done, &l, config, "", ui, communicator{}) go func() { i := 0 for range acceptC { diff --git a/provisioner/ansible/scp.go b/common/adapter/scp.go similarity index 99% rename from provisioner/ansible/scp.go rename to common/adapter/scp.go index ca029605d..06043ff20 100644 --- a/provisioner/ansible/scp.go +++ b/common/adapter/scp.go @@ -1,4 +1,4 @@ -package ansible +package adapter import ( "bufio" diff --git a/provisioner/ansible/adapter_test.go b/provisioner/ansible/adapter_test.go deleted file mode 100644 index 29667cbe2..000000000 --- a/provisioner/ansible/adapter_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package ansible - -import ( - "errors" - "io" - "log" - "net" - "os" - "testing" - "time" - - "github.com/hashicorp/packer/packer" - - "golang.org/x/crypto/ssh" -) - -func TestAdapter_Serve(t *testing.T) { - - // done signals the adapter that the provisioner is done - done := make(chan struct{}) - - acceptC := make(chan struct{}) - l := listener{done: make(chan struct{}), acceptC: acceptC} - - config := &ssh.ServerConfig{} - - ui := new(packer.NoopUi) - - sut := newAdapter(done, &l, config, "", newUi(ui), communicator{}) - go func() { - i := 0 - for range acceptC { - i++ - if i == 4 { - close(done) - l.Close() - } - } - }() - - sut.Serve() -} - -type listener struct { - done chan struct{} - acceptC chan<- struct{} - i int -} - -func (l *listener) Accept() (net.Conn, error) { - log.Println("Accept() called") - l.acceptC <- struct{}{} - select { - case <-l.done: - log.Println("done, serving an error") - return nil, errors.New("listener is closed") - - case <-time.After(10 * time.Millisecond): - l.i++ - - if l.i%2 == 0 { - c1, c2 := net.Pipe() - - go func(c net.Conn) { - <-time.After(100 * time.Millisecond) - log.Println("closing c") - c.Close() - }(c1) - - return c2, nil - } - } - - return nil, errors.New("accept error") -} - -func (l *listener) Close() error { - close(l.done) - return nil -} - -func (l *listener) Addr() net.Addr { - return addr{} -} - -type addr struct{} - -func (a addr) Network() string { - return a.String() -} - -func (a addr) String() string { - return "test" -} - -type communicator struct{} - -func (c communicator) Start(*packer.RemoteCmd) error { - return errors.New("communicator not supported") -} - -func (c communicator) Upload(string, io.Reader, *os.FileInfo) error { - return errors.New("communicator not supported") -} - -func (c communicator) UploadDir(dst string, src string, exclude []string) error { - return errors.New("communicator not supported") -} - -func (c communicator) Download(string, io.Writer) error { - return errors.New("communicator not supported") -} - -func (c communicator) DownloadDir(src string, dst string, exclude []string) error { - return errors.New("communicator not supported") -} diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 9979fbd2a..7ea29f584 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -26,6 +26,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/adapter" commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" @@ -63,7 +64,7 @@ type Config struct { type Provisioner struct { config Config - adapter *adapter + adapter *adapter.Adapter done chan struct{} ansibleVersion string ansibleMajVersion uint @@ -286,7 +287,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } ui = newUi(ui) - p.adapter = newAdapter(p.done, localListener, config, p.config.SFTPCmd, ui, comm) + p.adapter = adapter.NewAdapter(p.done, localListener, config, p.config.SFTPCmd, ui, comm) defer func() { log.Print("shutting down the SSH proxy") diff --git a/provisioner/inspec/adapter.go b/provisioner/inspec/adapter.go deleted file mode 100644 index ebd52d0a3..000000000 --- a/provisioner/inspec/adapter.go +++ /dev/null @@ -1,285 +0,0 @@ -package inspec - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - "log" - "net" - - "github.com/hashicorp/packer/packer" - "golang.org/x/crypto/ssh" -) - -// An adapter satisfies SSH requests (from an Inspec client) by delegating SSH -// exec and subsystem commands to a packer.Communicator. -type adapter struct { - done <-chan struct{} - l net.Listener - config *ssh.ServerConfig - ui packer.Ui - comm packer.Communicator -} - -func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, ui packer.Ui, comm packer.Communicator) *adapter { - return &adapter{ - done: done, - l: l, - config: config, - ui: ui, - comm: comm, - } -} - -func (c *adapter) Serve() { - log.Printf("SSH proxy: serving on %s", c.l.Addr()) - - for { - // Accept will return if either the underlying connection is closed or if a connection is made. - // after returning, check to see if c.done can be received. If so, then Accept() returned because - // the connection has been closed. - conn, err := c.l.Accept() - select { - case <-c.done: - return - default: - if err != nil { - c.ui.Error(fmt.Sprintf("listen.Accept failed: %v", err)) - continue - } - go func(conn net.Conn) { - if err := c.Handle(conn, c.ui); err != nil { - c.ui.Error(err.Error()) - } - }(conn) - } - } -} - -func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error { - log.Print("SSH proxy: accepted connection") - _, chans, reqs, err := ssh.NewServerConn(conn, c.config) - if err != nil { - return errors.New("failed to handshake") - } - - // discard all global requests - go ssh.DiscardRequests(reqs) - - // Service the incoming NewChannels - for newChannel := range chans { - if newChannel.ChannelType() != "session" { - newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") - continue - } - - go func(ch ssh.NewChannel) { - if err := c.handleSession(ch); err != nil { - c.ui.Error(err.Error()) - } - }(newChannel) - } - - return nil -} - -func (c *adapter) handleSession(newChannel ssh.NewChannel) error { - channel, requests, err := newChannel.Accept() - if err != nil { - return err - } - defer channel.Close() - - done := make(chan struct{}) - - // Sessions have requests such as "pty-req", "shell", "env", and "exec". - // see RFC 4254, section 6 - go func(in <-chan *ssh.Request) { - env := make([]envRequestPayload, 4) - for req := range in { - switch req.Type { - case "pty-req": - log.Println("inspec provisioner pty-req request") - // accept pty-req requests, but don't actually do anything. Necessary for OpenSSH and sudo. - req.Reply(true, nil) - - case "env": - req, err := newEnvRequest(req) - if err != nil { - c.ui.Error(err.Error()) - req.Reply(false, nil) - continue - } - env = append(env, req.Payload) - log.Printf("new env request: %s", req.Payload) - req.Reply(true, nil) - case "exec": - req, err := newExecRequest(req) - if err != nil { - c.ui.Error(err.Error()) - req.Reply(false, nil) - close(done) - continue - } - - log.Printf("new exec request: %s", req.Payload) - - if len(req.Payload) == 0 { - req.Reply(false, nil) - close(done) - return - } - - go func(channel ssh.Channel) { - exit := c.exec(string(req.Payload), channel, channel, channel.Stderr()) - - exitStatus := make([]byte, 4) - binary.BigEndian.PutUint32(exitStatus, uint32(exit)) - channel.SendRequest("exit-status", false, exitStatus) - close(done) - }(channel) - req.Reply(true, nil) - case "subsystem": - req, err := newSubsystemRequest(req) - if err != nil { - c.ui.Error(err.Error()) - req.Reply(false, nil) - continue - } - - log.Printf("new subsystem request: %s", req.Payload) - - c.ui.Error(fmt.Sprintf("unsupported subsystem requested: %s", req.Payload)) - req.Reply(false, nil) - default: - log.Printf("rejecting %s request", req.Type) - req.Reply(false, nil) - } - } - }(requests) - - <-done - return nil -} - -func (c *adapter) Shutdown() { - c.l.Close() -} - -func (c *adapter) exec(command string, in io.Reader, out io.Writer, err io.Writer) int { - var exitStatus int - exitStatus = c.remoteExec(command, in, out, err) - return exitStatus -} - -func (c *adapter) remoteExec(command string, in io.Reader, out io.Writer, err io.Writer) int { - cmd := &packer.RemoteCmd{ - Stdin: in, - Stdout: out, - Stderr: err, - Command: command, - } - - if err := c.comm.Start(cmd); err != nil { - c.ui.Error(err.Error()) - return cmd.ExitStatus - } - - cmd.Wait() - - return cmd.ExitStatus -} - -type envRequest struct { - *ssh.Request - Payload envRequestPayload -} - -type envRequestPayload struct { - Name string - Value string -} - -func (p envRequestPayload) String() string { - return fmt.Sprintf("%s=%s", p.Name, p.Value) -} - -func newEnvRequest(raw *ssh.Request) (*envRequest, error) { - r := new(envRequest) - r.Request = raw - - if err := ssh.Unmarshal(raw.Payload, &r.Payload); err != nil { - return nil, err - } - - return r, nil -} - -func sshString(buf io.Reader) (string, error) { - var size uint32 - err := binary.Read(buf, binary.BigEndian, &size) - if err != nil { - return "", err - } - - b := make([]byte, size) - err = binary.Read(buf, binary.BigEndian, b) - if err != nil { - return "", err - } - return string(b), nil -} - -type execRequest struct { - *ssh.Request - Payload execRequestPayload -} - -type execRequestPayload string - -func (p execRequestPayload) String() string { - return string(p) -} - -func newExecRequest(raw *ssh.Request) (*execRequest, error) { - r := new(execRequest) - r.Request = raw - buf := bytes.NewReader(r.Request.Payload) - - var err error - var payload string - if payload, err = sshString(buf); err != nil { - return nil, err - } - - r.Payload = execRequestPayload(payload) - return r, nil -} - -type subsystemRequest struct { - *ssh.Request - Payload subsystemRequestPayload -} - -type subsystemRequestPayload string - -func (p subsystemRequestPayload) String() string { - return string(p) -} - -func newSubsystemRequest(raw *ssh.Request) (*subsystemRequest, error) { - r := new(subsystemRequest) - r.Request = raw - buf := bytes.NewReader(r.Request.Payload) - - var err error - var payload string - if payload, err = sshString(buf); err != nil { - return nil, err - } - - r.Payload = subsystemRequestPayload(payload) - return r, nil -} diff --git a/provisioner/inspec/provisioner.go b/provisioner/inspec/provisioner.go index c5e2f747f..f2505351d 100644 --- a/provisioner/inspec/provisioner.go +++ b/provisioner/inspec/provisioner.go @@ -25,6 +25,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/adapter" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -58,7 +59,7 @@ type Config struct { type Provisioner struct { config Config - adapter *adapter + adapter *adapter.Adapter done chan struct{} inspecVersion string inspecMajVersion uint @@ -275,7 +276,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } ui = newUi(ui) - p.adapter = newAdapter(p.done, localListener, config, ui, comm) + p.adapter = adapter.NewAdapter(p.done, localListener, config, "", ui, comm) defer func() { log.Print("shutting down the SSH proxy")