Merge pull request #1 from drone-plugins/feature/unification

Multiple minor improvements, more unification
This commit is contained in:
Jack Spirou 2016-01-03 12:24:34 -05:00
commit af49113a20
8 changed files with 180 additions and 93 deletions

View File

@ -1,14 +1,10 @@
build:
image: golang:1.5
environment:
- GO15VENDOREXPERIMENT=1
- GOOS=linux
- GOARCH=amd64
- CGO_ENABLED=0
commands:
- go get
- go build
- go test
- make deps
- make vet
- make build
- make test
publish:
docker:
@ -21,7 +17,7 @@ publish:
plugin:
name: SSH
desc: Use SSH to execute commands on a remote host
desc: Execute commands on a remote host through SSH
type: deploy
image: plugins/drone-ssh
labels:

2
.gitignore vendored
View File

@ -23,4 +23,4 @@ _testmain.go
*.test
*.prof
drone-ssh
drone-ssh

View File

@ -1,8 +1,14 @@
# Docker image for the Drone build runner
# Docker image for the Drone Swift plugin
#
# CGO_ENABLED=0 go build -a -tags netgo
# docker build --rm=true -t plugins/drone-ssh .
# cd $GOPATH/src/github.com/drone-plugins/drone-ssh
# make deps build docker
FROM alpine:3.3
RUN apk update && \
apk add \
ca-certificates && \
rm -rf /var/cache/apk/*
FROM gliderlabs/alpine:3.1
ADD drone-ssh /bin/
ENTRYPOINT ["/bin/drone-ssh"]
ENTRYPOINT ["/bin/drone-ssh"]

31
Makefile Normal file
View File

@ -0,0 +1,31 @@
.PHONY: clean deps test build docker
export GOOS ?= linux
export GOARCH ?= amd64
export CGO_ENABLED ?= 0
CI_BUILD_NUMBER ?= 0
LDFLAGS += -X "main.buildDate=$(shell date -u '+%Y-%m-%d %H:%M:%S %Z')"
LDFLAGS += -X "main.build=$(CI_BUILD_NUMBER)"
clean:
go clean -i ./...
deps:
go get -t ./...
test:
go test -cover ./...
fmt:
go fmt ./...
vet:
go vet ./...
build:
go build -ldflags '-s -w $(LDFLAGS)'
docker:
docker build --rm=true -t plugins/drone-ssh .

View File

@ -1,2 +1,82 @@
# drone-ssh
Drone plugin for executing remote ssh commands
[![Build Status](http://beta.drone.io/api/badges/drone-plugins/drone-ssh/status.svg)](http://beta.drone.io/drone-plugins/drone-ssh)
[![](https://badge.imagelayers.io/plugins/drone-ssh:latest.svg)](https://imagelayers.io/?images=plugins/drone-ssh:latest 'Get your own badge on imagelayers.io')
Drone plugin for executing commands on a remote host through SSH
## Usage
```
./drone-ssh <<EOF
{
"repo" : {
"owner": "foo",
"name": "bar",
"full_name": "foo/bar"
},
"build" : {
"number": 22,
"status": "success",
"started_at": 1421029603,
"finished_at": 1421029813,
"commit": "9f2849d5",
"branch": "master",
"message": "Update the Readme",
"author": "johnsmith",
"author_email": "john.smith@gmail.com"
},
"vargs": {
"host": "foo.com",
"user": "root",
"port": 22,
"commands": [
"echo hello",
"echo world"
]
}
}
EOF
```
## Docker
Build the Docker container using `make`:
```
make deps build docker
```
### Example
```sh
docker run -i plugins/drone-ssh <<EOF
{
"repo" : {
"owner": "foo",
"name": "bar",
"full_name": "foo/bar"
},
"build" : {
"number": 22,
"status": "success",
"started_at": 1421029603,
"finished_at": 1421029813,
"commit": "9f2849d5",
"branch": "master",
"message": "Update the Readme",
"author": "johnsmith",
"author_email": "john.smith@gmail.com"
},
"vargs": {
"host": "foo.com",
"user": "root",
"port": 22,
"commands": [
"echo hello",
"echo world"
]
}
}
EOF
```

64
main.go
View File

@ -8,83 +8,85 @@ import (
"strings"
"time"
"github.com/drone/drone-plugin-go/plugin"
"github.com/drone/drone-go/drone"
"github.com/drone/drone-go/plugin"
"golang.org/x/crypto/ssh"
)
// Params stores the git clone parameters used to
// configure and customzie the git clone behavior.
type Params struct {
Commands []string `json:"commands"`
Login string `json:"user"`
Port int `json:"port"`
Host StrSlice `json:"host"`
Sleep int `json:"sleep"`
}
var (
build string
buildDate string
)
func main() {
v := new(Params)
w := new(plugin.Workspace)
plugin.Param("workspace", w)
plugin.Param("vargs", &v)
fmt.Printf("Drone SSH Plugin built at %s\n", buildDate)
workspace := drone.Workspace{}
vargs := Params{}
plugin.Param("workspace", &workspace)
plugin.Param("vargs", &vargs)
plugin.MustParse()
for i, host := range v.Host.Slice() {
err := run(w.Keys, v, host)
for i, host := range vargs.Host.Slice() {
err := run(workspace.Keys, &vargs, host)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if v.Sleep != 0 && i != v.Host.Len()-1 {
fmt.Printf("$ sleep %d\n", v.Sleep)
time.Sleep(time.Duration(v.Sleep) * time.Second)
if vargs.Sleep != 0 && i != vargs.Host.Len()-1 {
fmt.Printf("$ sleep %d\n", vargs.Sleep)
time.Sleep(time.Duration(vargs.Sleep) * time.Second)
}
}
}
func run(keys *plugin.Keypair, params *Params, host string) error {
// if no username is provided assume root
if len(params.Login) == 0 {
func run(key *drone.Key, params *Params, host string) error {
if params.Login == "" {
params.Login = "root"
}
// if no port is provided use default
if params.Port == 0 {
params.Port = 22
}
// join the host and port if necessary
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(key.Private))
signer, err := ssh.ParsePrivateKey([]byte(keys.Private))
if err != nil {
return fmt.Errorf("Error parsing private key. %s.", err)
return fmt.Errorf("Error: Failed to parse private key. %s", err)
}
config := &ssh.ClientConfig{
User: params.Login,
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
return fmt.Errorf("Error dialing server. %s.", err)
return fmt.Errorf("Error: Failed to dial to server. %s", err)
}
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("Error starting ssh session. %s.", err)
return fmt.Errorf("Error: Failed to start a SSH session. %s", err)
}
defer session.Close()
session.Stdout = os.Stdout
session.Stderr = os.Stderr
return session.Run(strings.Join(params.Commands, "\n"))
}

View File

@ -5,7 +5,7 @@ import (
"os"
"testing"
"github.com/drone/drone-plugin-go/plugin"
"github.com/drone/drone-go/drone"
)
var (
@ -15,14 +15,13 @@ var (
)
func TestRun(t *testing.T) {
// only runs the test if a host server is provided
if len(host) == 0 {
t.Skipf("TEST_SSH_HOST not provided")
return
}
out, err := ioutil.ReadFile(key)
if err != nil {
t.Errorf("Unable to read or find a test privte key. %s", err)
}
@ -30,14 +29,19 @@ func TestRun(t *testing.T) {
params := &Params{
Commands: []string{"whoami", "time", "ps -ax"},
Login: user,
Host: StrSlice{[]string{host}},
Host: drone.NewStringSlice(
[]string{
host,
},
),
}
keys := &plugin.Keypair{
keys := &drone.Key{
Private: string(out),
}
err = run(keys, params, host)
if err != nil {
t.Errorf("Unable to run SSH commands. %s.", err)
}

View File

@ -1,45 +1,13 @@
package main
import "encoding/json"
import (
"github.com/drone/drone-go/drone"
)
// 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
type Params struct {
Commands []string `json:"commands"`
Login string `json:"user"`
Port int `json:"port"`
Host drone.StringSlice `json:"host"`
Sleep int `json:"sleep"`
}