Migrated plugin to 0.5 structure

This commit is contained in:
Thomas Boerger 2016-07-11 19:47:15 +02:00 committed by Thomas Boerger
parent 69b686b375
commit c816c75fc6
No known key found for this signature in database
GPG Key ID: 5A388F55283960B6
12 changed files with 254 additions and 307 deletions

View File

@ -1 +0,0 @@
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.zIZPFzihqcXgWK0D3eLbrSXYK_1PQznfeVzKjVBsEs9C1nHAm71GjtQ4WlFI17pgs3JMZYMCJGg5jylg3WMD2Fxqeomucv2dTpeg4R-qacuUerkBk_3fyThNh2IOqzNbr3hHPyyVwtelXk6eInJQhoa6HBaXfpPTW0_x5JHNGeMuqWIXpZceLENm46t9zh-DsqnrXnhay4gB6kjoYAM8fxV4bRYbj-ef8XTg4G6ZS7dlTZdrOWIvFmYWNGF4dnRFN7EtxvkT7zTutAeNa4wZsc-p3qX03LKsc3kNijUhLtywh1AzZ-wEiOa2nGLQov5SLEnmpQbwJZQ9meS---LG8Q.TaGSxaCL3H-MXwSp.E0OSE8VqYg1Bs8Qxo2uIi03HMQ5Cxg4THetXBK5Z5G2_Qxt_7LMo1eOmvpi_aHOEPrd53hz3Uzn0C4PTZiD6TfMG_viMiJVHpzBayN3ZjUAs7JRavheCXgnlO5u1kgyp3XmRW089JbBFl2fMs8a7Bob8jLoQPJFyEIBGEJuUayE3pmSQ98Hhw3u1KAxbLG9iJqWErICNEZoSWPfhuWU-K5FMtxP0Ewx0ceqHKhJTQdqZycsFS7aeOgq5MtUMF3T7nsFX-tnrdv4siCiYT4kI9nGTQCCW3i3-nLq9ZkD5JbLs_IG1sfVNabd1hI2OpjAfL717Mp37AFbd0dPhlU7kxUytrTwiU3JVd1vr6EA1gd5TGA3CHOxjeVLpfHXMW6SJKTE3a0Wgi_YsWz336JkMkQkywgv8g6A0mpUhMcEt_Is.sSdenvAYiKXLuRdPszf25A

View File

@ -1,39 +1,42 @@
build: workspace:
image: golang:1.5 base: /go
environment: path: src/github.com/drone-plugins/drone-ssh
- CGO_ENABLED=0
commands:
- make deps
- make vet
- make build
- make test
publish: pipeline:
test:
image: golang:1.6
environment:
- CGO_ENABLED=0
commands:
- go vet
- go test -cover -coverprofile=coverage.out
- go build -ldflags "-s -w -X main.build=$DRONE_BUILD_NUMBER"
coverage: coverage:
when: when:
branch: master branch: master
docker: event: push
username: $$DOCKER_USER latest:
password: $$DOCKER_PASS image: docker
email: $$DOCKER_EMAIL storage_driver: overlay
repo: plugins/drone-ssh repo: plugins/ssh
tag: latest tags: [ "latest", "1.0", "1" ]
when: when:
branch: master branch: master
docker: event: push
username: $$DOCKER_USER develop:
password: $$DOCKER_PASS image: docker
email: $$DOCKER_EMAIL storage_driver: overlay
repo: plugins/drone-ssh repo: plugins/ssh
tag: develop tags: [ "develop" ]
when: when:
branch: develop branch: develop
event: push
plugin: plugin:
name: SSH name: SSH
desc: Execute commands on a remote host through SSH desc: Execute commands on a remote host through SSH
type: deploy type: deploy
image: plugins/drone-ssh image: plugins/ssh
labels: labels:
- deploy - deploy
- ssh - ssh

1
.drone.yml.sig Normal file
View File

@ -0,0 +1 @@
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwogIHBhdGg6IHNyYy9naXRodWIuY29tL2Ryb25lLXBsdWdpbnMvZHJvbmUtc3NoCgpwaXBlbGluZToKICB0ZXN0OgogICAgaW1hZ2U6IGdvbGFuZzoxLjYKICAgIGVudmlyb25tZW50OgogICAgICAtIENHT19FTkFCTEVEPTAKICAgIGNvbW1hbmRzOgogICAgICAtIGdvIHZldAogICAgICAtIGdvIHRlc3QgLWNvdmVyIC1jb3ZlcnByb2ZpbGU9Y292ZXJhZ2Uub3V0CiAgICAgIC0gZ28gYnVpbGQgLWxkZmxhZ3MgIi1zIC13IC1YIG1haW4uYnVpbGQ9JERST05FX0JVSUxEX05VTUJFUiIKICBjb3ZlcmFnZToKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCiAgICAgIGV2ZW50OiBwdXNoCiAgbGF0ZXN0OgogICAgaW1hZ2U6IGRvY2tlcgogICAgc3RvcmFnZV9kcml2ZXI6IG92ZXJsYXkKICAgIHJlcG86IHBsdWdpbnMvc3NoCiAgICB0YWdzOiBbICJsYXRlc3QiLCAiMS4wIiwgIjEiIF0KICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCiAgICAgIGV2ZW50OiBwdXNoCiAgZGV2ZWxvcDoKICAgIGltYWdlOiBkb2NrZXIKICAgIHN0b3JhZ2VfZHJpdmVyOiBvdmVybGF5CiAgICByZXBvOiBwbHVnaW5zL3NzaAogICAgdGFnczogWyAiZGV2ZWxvcCIgXQogICAgd2hlbjoKICAgICAgYnJhbmNoOiBkZXZlbG9wCiAgICAgIGV2ZW50OiBwdXNoCgpwbHVnaW46CiAgbmFtZTogU1NICiAgZGVzYzogRXhlY3V0ZSBjb21tYW5kcyBvbiBhIHJlbW90ZSBob3N0IHRocm91Z2ggU1NICiAgdHlwZTogZGVwbG95CiAgaW1hZ2U6IHBsdWdpbnMvc3NoCiAgbGFiZWxzOgogICAgLSBkZXBsb3kKICAgIC0gc3NoCg.k_ZUu8xFHIBhnh3ysdNRuerUITgmD-BwVgyYMfhmtmw

1
.gitignore vendored
View File

@ -22,6 +22,7 @@ _testmain.go
*.exe *.exe
*.test *.test
*.prof *.prof
.env
coverage.out coverage.out
drone-ssh drone-ssh

64
DOCS.md
View File

@ -1,14 +1,48 @@
Use the SSH plugin to execute commands on a remote server. The following parameters are used to configure this plugin: Use the SSH plugin to execute commands on a remote server. You will need to
supply Drone with a private SSH key to being able to connect to a host.
* `host` - address or IP of the remote machine ## Overview
* `port` - port to connect to on the remote machine
* `user` - user to log in as on the remote machine
* `commands` - list of commands to execute
Example configuration in your .drone.yml file: The following parameters are used to configure the plugin:
* **host** - address or IP of the remote machine
* **port** - port to connect to on the remote machine
* **user** - user to log in as on the remote machine
* **key** - private SSH key for the remote machine
* **sleep** - sleep for seconds between host connections
* **commands** - list of commands to execute
The following secret values can be set to configure the plugin.
* **SSH_HOST** - corresponds to **host**
* **SSH_PORT** - corresponds to **port**
* **SSH_USER** - corresponds to **user**
* **SSH_KEY** - corresponds to **key**
* **SSH_SLEEP** - corresponds to **sleep**
It is highly recommended to put the **SSH_KEY** into a secret so it is not
exposed to users. This can be done using the drone-cli.
```bash
drone secret add --image=ssh \
octocat/hello-world SSH_KEY @${HOME}/.ssh/id_rsa
```
Then sign the YAML file after all secrets are added.
```bash
drone sign octocat/hello-world
```
See [secrets](http://readme.drone.io/0.5/usage/secrets/) for additional
information on secrets
## Examples
Example configuration in your .drone.yml file for a single host:
```yaml ```yaml
deploy: pipeline:
ssh: ssh:
host: foo.com host: foo.com
user: root user: root
@ -18,10 +52,10 @@ deploy:
- echo world - echo world
``` ```
Example multi-host configuration in your .drone.yml file: Example configuration in your .drone.yml file for multiple hosts:
```yaml ```yaml
deploy: pipeline:
ssh: ssh:
host: host:
- foo.com - foo.com
@ -34,10 +68,10 @@ deploy:
- echo world - 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. 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.
The above example also uses the `sleep` parameter. The sleep parameter instructs Drone to sleep for N seconds between host executions. The above example also uses the `sleep` parameter. The sleep parameter
instructs Drone to sleep for N seconds between host executions.
## 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.

View File

@ -1,13 +1,9 @@
# Docker image for the Drone Swift plugin FROM alpine:3.4
#
# cd $GOPATH/src/github.com/drone-plugins/drone-ssh
# make deps build docker
FROM alpine:3.3
RUN apk update && \ RUN apk update && \
apk add \ apk add \
ca-certificates && \ ca-certificates \
openssh-client && \
rm -rf /var/cache/apk/* rm -rf /var/cache/apk/*
ADD drone-ssh /bin/ ADD drone-ssh /bin/

View File

@ -1,34 +0,0 @@
.PHONY: all clean deps fmt vet test docker
EXECUTABLE ?= drone-ssh
IMAGE ?= plugins/$(EXECUTABLE)
COMMIT ?= $(shell git rev-parse --short HEAD)
LDFLAGS = -X "main.buildCommit=$(COMMIT)"
PACKAGES = $(shell go list ./... | grep -v /vendor/)
all: deps build test
clean:
go clean -i ./...
deps:
go get -t ./...
fmt:
go fmt $(PACKAGES)
vet:
go vet $(PACKAGES)
test:
@for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done;
docker:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-s -w $(LDFLAGS)'
docker build --rm -t $(IMAGE) .
$(EXECUTABLE): $(wildcard *.go)
go build -ldflags '-s -w $(LDFLAGS)'
build: $(EXECUTABLE)

115
README.md
View File

@ -6,106 +6,43 @@
Drone plugin to execute commands on a remote host through SSH. For the usage information and a listing of the available options please take a look at [the docs](DOCS.md). Drone plugin to execute commands on a remote host through SSH. For the usage information and a listing of the available options please take a look at [the docs](DOCS.md).
## Binary ## Build
Build the binary using `make`: Build the binary with the following commands:
``` ```
make deps build export GO15VENDOREXPERIMENT=1
``` go build
go test
### Example
```sh
./drone-ssh <<EOF
{
"repo": {
"clone_url": "git://github.com/drone/drone",
"owner": "drone",
"name": "drone",
"full_name": "drone/drone"
},
"system": {
"link_url": "https://beta.drone.io"
},
"build": {
"number": 22,
"status": "success",
"started_at": 1421029603,
"finished_at": 1421029813,
"message": "Update the Readme",
"author": "johnsmith",
"author_email": "john.smith@gmail.com"
"event": "push",
"branch": "master",
"commit": "436b7a6e2abaddfd35740527353e78a227ddcb2c",
"ref": "refs/heads/master"
},
"workspace": {
"root": "/drone/src",
"path": "/drone/src/github.com/drone/drone"
},
"vargs": {
"host": "foo.com",
"user": "root",
"port": 22,
"commands": [
"echo hello",
"echo world"
]
}
}
EOF
``` ```
## Docker ## Docker
Build the container using `make`: Build the docker image with the following commands:
``` ```
make deps docker export GO15VENDOREXPERIMENT=1
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -tags netgo
``` ```
### Example Please note incorrectly building the image for the correct x64 linux and with GCO disabled will result in an error when running the Docker image:
```
docker: Error response from daemon: Container command
'/bin/drone-ssh' not found or does not exist..
```
## Usage
Execute a single remote command
```sh ```sh
docker run -i plugins/drone-ssh <<EOF docker run --rm \
{ -e PLUGIN_HOST=foo.com \
"repo": { -e PLUGIN_USER=root \
"clone_url": "git://github.com/drone/drone", -e PLUGIN_KEY="$(cat ${HOME}/.ssh/id_rsa)" \
"owner": "drone", -e PLUGIN_COMMANDS=whoami \
"name": "drone", -v $(pwd)/$(pwd) \
"full_name": "drone/drone" -w $(pwd) \
}, plugins/ssh
"system": {
"link_url": "https://beta.drone.io"
},
"build": {
"number": 22,
"status": "success",
"started_at": 1421029603,
"finished_at": 1421029813,
"message": "Update the Readme",
"author": "johnsmith",
"author_email": "john.smith@gmail.com"
"event": "push",
"branch": "master",
"commit": "436b7a6e2abaddfd35740527353e78a227ddcb2c",
"ref": "refs/heads/master"
},
"workspace": {
"root": "/drone/src",
"path": "/drone/src/github.com/drone/drone"
},
"vargs": {
"host": "foo.com",
"user": "root",
"port": 22,
"commands": [
"echo hello",
"echo world"
]
}
}
EOF
``` ```

143
main.go
View File

@ -1,91 +1,80 @@
package main package main
import ( import (
"fmt" "log"
"net"
"os" "os"
"strconv"
"strings"
"time"
"github.com/drone/drone-go/drone" "github.com/joho/godotenv"
"github.com/drone/drone-go/plugin" "github.com/urfave/cli"
"golang.org/x/crypto/ssh"
) )
var ( var version string // build number set at compile-time
buildCommit string
)
func main() { func main() {
fmt.Printf("Drone SSH Plugin built from %s\n", buildCommit) app := cli.NewApp()
app.Name = "ssh plugin"
workspace := drone.Workspace{} app.Usage = "ssh plugin"
vargs := Params{} app.Action = run
app.Version = version
plugin.Param("workspace", &workspace) app.Flags = []cli.Flag{
plugin.Param("vargs", &vargs) cli.StringFlag{
plugin.MustParse() Name: "ssh-key",
Usage: "private ssh key",
for i, host := range vargs.Host.Slice() { EnvVar: "PLUGIN_SSH_KEY,SSH_KEY",
err := run(workspace.Keys, &vargs, host) },
cli.StringFlag{
if err != nil { Name: "user",
fmt.Println(err) Usage: "connect as user",
os.Exit(1) EnvVar: "PLUGIN_USER,SSH_USER",
} Value: "root",
},
if vargs.Sleep != 0 && i != vargs.Host.Len()-1 { cli.StringSliceFlag{
fmt.Printf("$ sleep %d\n", vargs.Sleep) Name: "host",
time.Sleep(time.Duration(vargs.Sleep) * time.Second) Usage: "connect to host",
} EnvVar: "PLUGIN_HOST,SSH_HOST",
} },
} cli.IntFlag{
Name: "port",
func run(key *drone.Key, params *Params, host string) error { Usage: "connect to port",
if params.Login == "" { EnvVar: "PLUGIN_PORT,SSH_PORT",
params.Login = "root" Value: 22,
} },
cli.IntFlag{
if params.Port == 0 { Name: "sleep",
params.Port = 22 Usage: "sleep between hosts",
} EnvVar: "PLUGIN_SLEEP,SSH_SLEEP",
},
addr := net.JoinHostPort( cli.StringSliceFlag{
host, Name: "commands",
strconv.Itoa(params.Port), Usage: "execute commands",
) EnvVar: "PLUGIN_COMMANDS",
},
fmt.Printf("$ ssh %s@%s -p %d\n", params.Login, addr, params.Port) cli.StringFlag{
signer, err := ssh.ParsePrivateKey([]byte(key.Private)) Name: "env-file",
Usage: "source env file",
if err != nil {
return fmt.Errorf("Error: Failed to parse private key. %s", err)
}
config := &ssh.ClientConfig{
User: params.Login,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
}, },
} }
client, err := ssh.Dial("tcp", addr, config) if err := app.Run(os.Args); err != nil {
log.Fatal(err)
if err != nil {
return fmt.Errorf("Error: Failed to dial to server. %s", err)
} }
}
session, err := client.NewSession()
func run(c *cli.Context) error {
if err != nil { if c.String("env-file") != "" {
return fmt.Errorf("Error: Failed to start a SSH session. %s", err) _ = godotenv.Load(c.String("env-file"))
} }
defer session.Close() plugin := Plugin{
Config: Config{
session.Stdout = os.Stdout Key: c.String("ssh-key"),
session.Stderr = os.Stderr User: c.String("user"),
Host: c.StringSlice("host"),
return session.Run(strings.Join(params.Commands, "\n")) Port: c.Int("port"),
Sleep: c.Int("sleep"),
Commands: c.StringSlice("commands"),
},
}
return plugin.Exec()
} }

View File

@ -1,48 +0,0 @@
package main
import (
"io/ioutil"
"os"
"testing"
"github.com/drone/drone-go/drone"
)
var (
host = os.Getenv("TEST_SSH_HOST")
user = os.Getenv("TEST_SSH_USER")
key = os.Getenv("TEST_SSH_KEY")
)
func TestRun(t *testing.T) {
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)
}
params := &Params{
Commands: []string{"whoami", "time", "ps -ax"},
Login: user,
Host: drone.NewStringSlice(
[]string{
host,
},
),
}
keys := &drone.Key{
Private: string(out),
}
err = run(keys, params, host)
if err != nil {
t.Errorf("Unable to run SSH commands. %s.", err)
}
}

82
plugin.go Normal file
View File

@ -0,0 +1,82 @@
package main
import (
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
"golang.org/x/crypto/ssh"
)
type (
Config struct {
Key string `json:"key"`
User string `json:"user"`
Host []string `json:"host"`
Port int `json:"port"`
Sleep int `json:"sleep"`
Commands []string `json:"commands"`
}
Plugin struct {
Config Config
}
)
func (p Plugin) Exec() error {
if p.Config.Key == "" {
return fmt.Errorf("Error: Can't connect without a private SSH key.")
}
for i, host := range p.Config.Host {
addr := net.JoinHostPort(
host,
strconv.Itoa(p.Config.Port),
)
signer, err := ssh.ParsePrivateKey([]byte(p.Config.Key))
if err != nil {
return fmt.Errorf("Error: Failed to parse private key. %s", err)
}
config := &ssh.ClientConfig{
User: p.Config.User,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}
fmt.Printf("+ ssh %s@%s -p %d\n", p.Config.User, addr, p.Config.Port)
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
return fmt.Errorf("Error: Failed to dial to server. %s", err)
}
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("Error: Failed to start a SSH session. %s", err)
}
defer session.Close()
session.Stdout = os.Stdout
session.Stderr = os.Stderr
if err := session.Run(strings.Join(p.Config.Commands, "\n")); err != nil {
return err
}
if p.Config.Sleep != 0 && i != len(p.Config.Host)-1 {
fmt.Printf("+ sleep %d\n", p.Config.Sleep)
time.Sleep(time.Duration(p.Config.Sleep) * time.Second)
}
}
return nil
}

View File

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