ability to run commands on multiple hosts

This commit is contained in:
Brad Rydzewski 2015-10-30 10:43:55 -07:00
parent 2821783224
commit a1294ae508
3 changed files with 78 additions and 9 deletions

21
DOCS.md
View File

@ -5,7 +5,7 @@ Use the SSH plugin to execute commands on a remote server. The following paramet
* `user` - user to log in as on the remote machine
* `commands` - list of commands to execute
The following is a sample SSH configuration in your .drone.yml file:
Example configuration in your .drone.yml file:
```yaml
deploy:
@ -18,4 +18,23 @@ deploy:
- echo world
```
Example multi-host configuration in your .drone.yml file:
```yaml
deploy:
ssh:
host:
- foo.com
- bar.com
user: root
port: 22
commands:
- echo hello
- echo world
```
In the above example Drone executes the commands on multiple hosts sequentially. If the commands fail on a single host this plugin exits immediatly, and will not run your commands on the remaining hosts in the list.
## Keys
The plugin authenticates to your server using a per-repository SSH key generated by Drone. You can find the public key in your repository settings in Drone. You will need to copy / paste this key into your `~/.ssh/authorized_keys` file on your remote machine.

21
main.go
View File

@ -17,7 +17,7 @@ type Params struct {
Commands []string `json:"commands"`
Login string `json:"user"`
Port int `json:"port"`
Host string `json:"host"`
Host StrSlice `json:"host"`
}
func main() {
@ -27,13 +27,15 @@ func main() {
plugin.Param("vargs", &v)
plugin.MustParse()
err := run(w.Keys, v)
if err != nil {
os.Exit(1)
for _, host := range v.Host.Slice() {
err := run(w.Keys, v, host)
if err != nil {
os.Exit(1)
}
}
}
func run(keys *plugin.Keypair, params *Params) error {
func run(keys *plugin.Keypair, params *Params, host string) error {
// if no username is provided assume root
if len(params.Login) == 0 {
@ -46,11 +48,14 @@ func run(keys *plugin.Keypair, params *Params) error {
}
// join the host and port if necessary
host := net.JoinHostPort(
params.Host,
addr := net.JoinHostPort(
host,
strconv.Itoa(params.Port),
)
// trace command used for debugging in the build logs
fmt.Printf("$ ssh %s@%s -p %d\n", params.Login, addr, params.Port)
signer, err := ssh.ParsePrivateKey([]byte(keys.Private))
if err != nil {
return fmt.Errorf("Error parsing private key. %s.", err)
@ -61,7 +66,7 @@ func run(keys *plugin.Keypair, params *Params) error {
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
}
client, err := ssh.Dial("tcp", host, config)
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
return fmt.Errorf("Error dialing server. %s.", err)
}

45
types.go Normal file
View File

@ -0,0 +1,45 @@
package main
import "encoding/json"
// StrSlice representes a string or an array of strings.
// We need to override the json decoder to accept both options.
type StrSlice struct {
parts []string
}
// UnmarshalJSON decodes the byte slice whether it's a string or an array of strings.
// This method is needed to implement json.Unmarshaler.
func (e *StrSlice) UnmarshalJSON(b []byte) error {
if len(b) == 0 {
return nil
}
p := make([]string, 0, 1)
if err := json.Unmarshal(b, &p); err != nil {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
p = append(p, s)
}
e.parts = p
return nil
}
// Len returns the number of parts of the StrSlice.
func (e *StrSlice) Len() int {
if e == nil {
return 0
}
return len(e.parts)
}
// Slice gets the parts of the StrSlice as a Slice of string.
func (e *StrSlice) Slice() []string {
if e == nil {
return nil
}
return e.parts
}