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:
image: golang:1.5
environment:
- CGO_ENABLED=0
commands:
- make deps
- make vet
- make build
- make test
workspace:
base: /go
path: src/github.com/drone-plugins/drone-ssh
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:
when:
branch: master
docker:
username: $$DOCKER_USER
password: $$DOCKER_PASS
email: $$DOCKER_EMAIL
repo: plugins/drone-ssh
tag: latest
event: push
latest:
image: docker
storage_driver: overlay
repo: plugins/ssh
tags: [ "latest", "1.0", "1" ]
when:
branch: master
docker:
username: $$DOCKER_USER
password: $$DOCKER_PASS
email: $$DOCKER_EMAIL
repo: plugins/drone-ssh
tag: develop
event: push
develop:
image: docker
storage_driver: overlay
repo: plugins/ssh
tags: [ "develop" ]
when:
branch: develop
event: push
plugin:
name: SSH
desc: Execute commands on a remote host through SSH
type: deploy
image: plugins/drone-ssh
image: plugins/ssh
labels:
- deploy
- 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
*.test
*.prof
.env
coverage.out
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
* `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
## Overview
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
deploy:
pipeline:
ssh:
host: foo.com
user: root
@ -18,10 +52,10 @@ deploy:
- echo world
```
Example multi-host configuration in your .drone.yml file:
Example configuration in your .drone.yml file for multiple hosts:
```yaml
deploy:
pipeline:
ssh:
host:
- foo.com
@ -34,10 +68,10 @@ deploy:
- 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.
## 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.
The above example also uses the `sleep` parameter. The sleep parameter
instructs Drone to sleep for N seconds between host executions.

View File

@ -1,13 +1,9 @@
# Docker image for the Drone Swift plugin
#
# cd $GOPATH/src/github.com/drone-plugins/drone-ssh
# make deps build docker
FROM alpine:3.3
FROM alpine:3.4
RUN apk update && \
apk add \
ca-certificates && \
ca-certificates \
openssh-client && \
rm -rf /var/cache/apk/*
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).
## Binary
## Build
Build the binary using `make`:
Build the binary with the following commands:
```
make deps build
```
### 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
export GO15VENDOREXPERIMENT=1
go build
go test
```
## 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
docker run -i plugins/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 run --rm \
-e PLUGIN_HOST=foo.com \
-e PLUGIN_USER=root \
-e PLUGIN_KEY="$(cat ${HOME}/.ssh/id_rsa)" \
-e PLUGIN_COMMANDS=whoami \
-v $(pwd)/$(pwd) \
-w $(pwd) \
plugins/ssh
```

143
main.go
View File

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