Skip to content

Commit

Permalink
Merge pull request #141 from moul/wrapper
Browse files Browse the repository at this point in the history
Add wrapper (#122)
  • Loading branch information
moul committed Apr 27, 2016
2 parents d86ea80 + 7f22c70 commit bc1a93a
Show file tree
Hide file tree
Showing 10 changed files with 512 additions and 53 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
3. [Configuration](#configuration)
4. [Usage](#usage)
5. [Install](#install)
* [Register the wrapper (optional)](#register-the-wrapper-optional)
6. [Getting started](#getting-started)
7. [Changelog](#changelog)
8. [Alternative version](#alternative-version)
Expand Down Expand Up @@ -303,6 +304,27 @@ brew install assh --HEAD

Get a released version on: https://github.com/moul/advanced-ssh-config/releases

---

#### Register the wrapper (optional)

To improve experience when using advanced pattern matching, add the following at the end of your .bashrc or .zshrc:

```bash
alias ssh="assh wrapper ssh"
```

This step is not *mandatory* but highly *recommended*.

---

**Note**: `ssh` does not understand advanced patterns;
To bypass this limitation, `assh` maintains a list of *known hosts* and regenerate the `~/.ssh/config` with all those expanded *known hosts*.

Without the wrapper, the `~/.ssh/config` risks to be outdated when connecting to a new host for the first time and you will need to launch the command again.

With the wrapper, `ssh` will *always* be called with an updated `~/.ssh/config` file.

## Getting started

1. Backup your old `~/.ssh/config`: `cp ~/.ssh/config ~/.ssh/config.backup`
Expand All @@ -314,6 +336,7 @@ Get a released version on: https://github.com/moul/advanced-ssh-config/releases

### master (unreleased)

* Add wrapper and `known_hosts` support to handle *advanced patterns* ([#122](https://github.com/moul/advanced-ssh-config/issues/122))
* Add build information in .ssh/config header ([#49](https://github.com/moul/advanced-ssh-config/issues/49))
* Add Autocomplete support ([#48](https://github.com/moul/advanced-ssh-config/issues/48))
* Initial `Aliases` support ([#133](https://github.com/moul/advanced-ssh-config/issues/133))
Expand Down
8 changes: 8 additions & 0 deletions contrib/homebrew/assh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ def install
zsh_completion.install "contrib/completion/zsh_autocomplete"
end

def caveats
<<-EOS.undent
To activate advanced pattern matching, add the following at the end of your .bashrc or .zshrc:
alias ssh="assh wrapper ssh"
EOS
end

test do
output = shell_output(bin/"assh --version")
assert output.include? "assh version 2"
Expand Down
12 changes: 12 additions & 0 deletions pkg/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,16 @@ var Commands = []cli.Command{
Usage: "Display system-wide information",
Action: cmdInfo,
},
{
Name: "wrapper",
Usage: "Initialize assh, then run ssh/scp/rsync...",
Subcommands: []cli.Command{
{
Name: "ssh",
Action: cmdWrapper,
Usage: "Wrap ssh",
Flags: config.SSHFlags,
},
},
},
}
32 changes: 23 additions & 9 deletions pkg/commands/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,36 @@ func cmdProxy(c *cli.Context) {
Logger.Fatalf("Cannot open configuration file: %v", err)
}

if err = conf.LoadKnownHosts(); err != nil {
Logger.Debugf("Failed to load assh known_hosts: %v", err)
}

target := c.Args()[0]

isOutdated, err := conf.IsConfigOutdated(target)
if err != nil {
Logger.Warnf("Cannot check if ~/.ssh/config is outdated.")
}
if isOutdated {
Logger.Debugf("The configuration file is outdated, rebuilding it before calling ssh")
Logger.Warnf("'~/.ssh/config' has been rewritten. SSH needs to be restarted. See https://github.com/moul/advanced-ssh-config/issues/122 for more information.")
Logger.Debugf("Saving SSH config")
err = conf.SaveSshConfig()
if err != nil {
Logger.Fatalf("Cannot save SSH config file: %v", err)
}
}

// FIXME: handle complete host with json

host, err := computeHost(c.Args()[0], c.Int("port"), conf)
host, err := computeHost(target, c.Int("port"), conf)
if err != nil {
Logger.Fatalf("Cannot get host '%s': %v", c.Args()[0], err)
Logger.Fatalf("Cannot get host '%s': %v", target, err)
}
w := Logger.Writer()
defer w.Close()
host.WriteSshConfigTo(w)

Logger.Debugf("Saving SSH config")
err = conf.SaveSshConfig()
if err != nil {
Logger.Fatalf("Cannot save SSH config file: %v", err)
}

Logger.Debugf("Proxying")
err = proxy(host, conf, dryRun)
if err != nil {
Expand Down Expand Up @@ -138,8 +152,8 @@ func proxyDirect(host *config.Host, dryRun bool) error {

func proxyCommand(host *config.Host, command string, dryRun bool) error {
command = host.ExpandString(command)
args, err := shlex.Split(command)
Logger.Debugf("ProxyCommand: %s", command)
args, err := shlex.Split(command)
if err != nil {
return err
}
Expand Down
50 changes: 50 additions & 0 deletions pkg/commands/proxy_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"fmt"
"strings"
"testing"

Expand All @@ -21,6 +22,8 @@ hosts:
User: toor
"*.ddd":
HostName: 1.3.5.7
eee:
ResolveCommand: /bin/sh -c "echo 42.42.42.42"
defaults:
Port: 22
User: root
Expand Down Expand Up @@ -55,3 +58,50 @@ func TestComputeHost(t *testing.T) {
So(host.Port, ShouldEqual, "42")
})
}

func Test_proxyCommand(t *testing.T) {
Convey("Testing proxyCommand()", t, func() {
// FIXME: test stdout
config := config.New()
err := config.LoadConfig(strings.NewReader(configExample))
host, err := computeHost("aaa", 0, config)
So(err, ShouldBeNil)

err = proxyCommand(host, "echo test from proxyCommand", false)
So(err, ShouldBeNil)

err = proxyCommand(host, "/bin/sh -c 'echo test from proxyCommand'", false)
So(err, ShouldBeNil)

err = proxyCommand(host, "/bin/sh -c 'exit 1'", false)
So(err, ShouldNotBeNil)

err = proxyCommand(host, "blah", true)
So(err, ShouldResemble, fmt.Errorf("dry-run: Execute [blah]"))
})
}

func Test_hostPrepare(t *testing.T) {
Convey("Testing hostPrepare()", t, func() {
config := config.New()
err := config.LoadConfig(strings.NewReader(configExample))

host, err := computeHost("aaa", 0, config)
So(err, ShouldBeNil)
So(host.HostName, ShouldEqual, "1.2.3.4")
So(hostPrepare(host), ShouldBeNil)
So(host.HostName, ShouldEqual, "1.2.3.4")

host, err = computeHost("bbb", 0, config)
So(err, ShouldBeNil)
So(host.HostName, ShouldEqual, "bbb")
So(hostPrepare(host), ShouldBeNil)
So(host.HostName, ShouldEqual, "bbb")

host, err = computeHost("eee", 0, config)
So(err, ShouldBeNil)
So(host.HostName, ShouldEqual, "eee")
So(hostPrepare(host), ShouldBeNil)
So(host.HostName, ShouldEqual, "42.42.42.42")
})
}
70 changes: 70 additions & 0 deletions pkg/commands/wrapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package commands

import (
"fmt"
"os"
"os/exec"
"syscall"

"github.com/codegangsta/cli"

"github.com/moul/advanced-ssh-config/pkg/config"
. "github.com/moul/advanced-ssh-config/pkg/logger"
)

func cmdWrapper(c *cli.Context) {
if len(c.Args()) < 1 {
Logger.Fatalf("Missing <target> argument. See usage with 'assh wrapper %s -h'.", c.Command.Name)
}

// prepare variables
target := c.Args()[0]
command := c.Args()[1:]
options := []string{}
for _, flag := range config.SSHBoolFlags {
if c.Bool(flag) {
options = append(options, fmt.Sprintf("-%s", flag))
}
}
for _, flag := range config.SSHStringFlags {
if val := c.String(flag); val != "" {
options = append(options, fmt.Sprintf("-%s", flag))
options = append(options, val)
}
}
args := []string{c.Command.Name}
args = append(args, options...)
args = append(args, target)
args = append(args, command...)
bin, err := exec.LookPath(c.Command.Name)
if err != nil {
Logger.Fatalf("Cannot find %q in $PATH", c.Command.Name)
}

Logger.Debugf("Wrapper called with bin=%v target=%v command=%v options=%v, args=%v", bin, target, command, options, args)

// check if config is up-to-date
conf, err := config.Open()
if err != nil {
Logger.Fatalf("Cannot open configuration file: %v", err)
}

if err = conf.LoadKnownHosts(); err != nil {
Logger.Debugf("Failed to load assh known_hosts: %v", err)
}

// check if .ssh/config is outdated
isOutdated, err := conf.IsConfigOutdated(target)
if err != nil {
Logger.Error(err)
}
if isOutdated {
Logger.Debugf("The configuration file is outdated, rebuilding it before calling %s", c.Command.Name)
if err = conf.SaveSshConfig(); err != nil {
Logger.Error(err)
}
}

// Execute Binary
syscall.Exec(bin, args, os.Environ())
}
Loading

0 comments on commit bc1a93a

Please sign in to comment.