Skip to content
This repository has been archived by the owner on Jan 21, 2020. It is now read-only.

Commit

Permalink
Playbook SSH backend supports localhost (#902)
Browse files Browse the repository at this point in the history
Signed-off-by: David Chung <david.chung@docker.com>
  • Loading branch information
David Chung committed May 1, 2018
1 parent b5de550 commit 6dd56db
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 28 deletions.
84 changes: 61 additions & 23 deletions pkg/callable/backend/ssh/script.go
Expand Up @@ -19,10 +19,10 @@ var log = logutil.New("module", "cli/backend/ssh")

func init() {
backend.Register("ssh", Script, func(params backend.Parameters) {
params.StringSlice("hostport", []string{}, "Host:port eg. localhost:22")
params.StringSlice("hostport", []string{}, "Host:port eg. 10.10.100.101:22 or `localhost`")
params.String("user", "", "username")
params.String("password", "", "password")
params.String("keyfile", "", "keyfile e.g. $HOME/.ssh/id_rsa")
params.String("keyfile", "", "keyfile[:user] e.g. $HOME/.ssh/id_rsa, if [:user] present, sets user too")
})
}

Expand All @@ -38,6 +38,7 @@ func Script(scope scope.Scope, test bool, opt ...interface{}) (backend.ExecFunc,
if err != nil {
return err
}

user, err := parameters.GetString("user")
if err != nil {
return err
Expand Down Expand Up @@ -74,52 +75,89 @@ func Script(scope scope.Scope, test bool, opt ...interface{}) (backend.ExecFunc,

var base ssh.Conn
if keyfile != "" {
if parts := strings.SplitN(keyfile, ":", 2); len(parts) == 2 && user == "" {
keyfile = parts[0]
user = parts[1]
}
base.Config = ssh.PublicKeyConfig(user, keyfile)
log.Debug("using public key auth", "user", user, "keyfile", keyfile)
} else if password != "" {
} else if password != "" && user != "" {
base.Config = ssh.UsernamePasswordConfig(user, password)
log.Debug("using password auth", "user", user)
} else {
} else if user == "" {
base.Config = ssh.AgentConfig(user)
log.Debug("using ssh agent auth", "user", user)
} else {
return fmt.Errorf("Canot auth: missing user")
}

var wg sync.WaitGroup

for _, hostport := range hostports {

cl := base
cl.Remote = ssh.HostPort(hostport)

log.Debug("running", "remote", cl.Remote)
if len(hostports) == 0 {
hostports = append(hostports, "localhost")
}

wg.Add(1)
go func() {
defer wg.Done()
for _, hostport := range hostports {

exec, err := cl.Exec()
if err != nil {
log.Error("cannot connect", "remote", cl.Remote, "err", err)
return
switch hostport {
case "localhost":
wg.Add(1)
go func() {
defer wg.Done()
if err := execScript(nil, script, args, out); err != nil {
log.Error("error", "remote", "localhost", "err", err)
return
}
}()

default:

// Default port is 22 if not specified
if strings.Index(hostport, ":") < 0 {
hostport += ":22"
}
if err := execScript(exec, script, args, out); err != nil {
log.Error("error", "remote", cl.Remote, "err", err)
return
}
}()

cl := base
cl.Remote = ssh.HostPort(hostport)

log.Debug("running", "remote", cl.Remote)

wg.Add(1)
go func() {
defer wg.Done()

exec, err := cl.Exec()
if err != nil {
log.Error("cannot connect", "remote", cl.Remote, "err", err)
return
}
if err := execScript(exec, script, args, out); err != nil {
log.Error("error", "remote", cl.Remote, "err", err)
return
}
}()
}
}

wg.Wait()
return nil
}, nil
}

// impl == nil when running on localhost
func execScript(impl exec.Interface, script string, args []string, out io.Writer) error {
cmd := strings.Join(append([]string{"/bin/sh"}, args...), " ")
log.Debug("sh", "cmd", cmd)

run := exec.Command(cmd)
run.WithExec(impl).StartWithHandlers(

if impl != nil {
run = run.WithExec(impl)
} else {
run = run.InheritEnvs(true)
}

run.StartWithHandlers(
func(stdin io.Writer) error {
_, err := stdin.Write([]byte(script))
return err
Expand Down
3 changes: 0 additions & 3 deletions pkg/callable/callable.go
Expand Up @@ -77,9 +77,6 @@ type Callable struct {
lock sync.RWMutex
}

// TemplateFunc is a function that returns the template
type TemplateFunc func(url string, opts template.Options) (*template.Template, error)

// NewCallable creates a callable
func NewCallable(scope scope.Scope, src string, parameters backend.Parameters, options Options) *Callable {
// Note that because Callable embeds Parameters and implements the methods in Parameters, a Callable
Expand Down
4 changes: 2 additions & 2 deletions pkg/template/integration_test.go
Expand Up @@ -307,14 +307,14 @@ func TestSourceWithHeaders(t *testing.T) {
// "A": "B",
// "Accept": "*/*",
// "Connection": "close",
// "Foo": "Bar",
// "Foo": "Bar,Bar",
// "Host": "httpbin.org",
// "User-Agent": "curl/7.43.0"
// }
// }

require.Equal(t, map[string]interface{}{
"Foo": "Bar",
"Foo": "Bar,Bar",
"Host": "httpbin.org",
"User-Agent": "Go-http-client/1.1",
"A": "B",
Expand Down

0 comments on commit 6dd56db

Please sign in to comment.