mirror of
https://github.com/appleboy/drone-ssh.git
synced 2026-06-16 14:49:25 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9cc37282c | |||
| 6e431b0c53 | |||
| 3499506089 | |||
| 6c0b475c15 | |||
| 60993a71e2 | |||
| 8bfc58f9d0 | |||
| 7f4cb1c1d0 | |||
| f92f762c9d | |||
| 84cb184039 | |||
| 31c084fd3e |
+8
-9
@@ -10,7 +10,7 @@ clone:
|
||||
|
||||
pipeline:
|
||||
lint:
|
||||
image: appleboy/golang-testing
|
||||
image: golang:1.11
|
||||
pull: true
|
||||
group: golang
|
||||
commands:
|
||||
@@ -19,21 +19,21 @@ pipeline:
|
||||
- make test-vendor
|
||||
|
||||
linux_amd64:
|
||||
image: appleboy/golang-testing
|
||||
image: golang:1.11
|
||||
pull: true
|
||||
group: golang
|
||||
commands:
|
||||
- make linux_amd64
|
||||
|
||||
linux_arm64:
|
||||
image: appleboy/golang-testing
|
||||
image: golang:1.11
|
||||
pull: true
|
||||
group: golang
|
||||
commands:
|
||||
- make linux_arm64
|
||||
|
||||
linux_arm:
|
||||
image: appleboy/golang-testing
|
||||
image: golang:1.11
|
||||
pull: true
|
||||
group: golang
|
||||
commands:
|
||||
@@ -49,7 +49,7 @@ pipeline:
|
||||
- make coverage
|
||||
|
||||
release:
|
||||
image: appleboy/golang-testing
|
||||
image: golang:1.11
|
||||
pull: true
|
||||
commands:
|
||||
- make release
|
||||
@@ -88,10 +88,9 @@ pipeline:
|
||||
event: [ tag ]
|
||||
local: false
|
||||
|
||||
facebook:
|
||||
image: appleboy/drone-facebook
|
||||
secrets: [ fb_page_token, fb_verify_token ]
|
||||
discord:
|
||||
image: appleboy/drone-discord
|
||||
pull: true
|
||||
to: 1234973386524610
|
||||
secrets: [ discord_webhook_id, discord_webhook_token ]
|
||||
when:
|
||||
status: [ changed, failure ]
|
||||
|
||||
@@ -75,40 +75,6 @@ pipeline:
|
||||
+ proxy_password: 1234
|
||||
```
|
||||
|
||||
Example configuration for `master` branch:
|
||||
|
||||
```diff
|
||||
pipeline:
|
||||
ssh:
|
||||
image: appleboy/drone-ssh
|
||||
host: foo.com
|
||||
username: root
|
||||
password: 1234
|
||||
port: 22
|
||||
script:
|
||||
- echo hello
|
||||
- echo world
|
||||
+ when:
|
||||
+ branch: master
|
||||
```
|
||||
|
||||
Example configuration for `tag` event:
|
||||
|
||||
```diff
|
||||
pipeline:
|
||||
ssh:
|
||||
image: appleboy/drone-ssh
|
||||
host: foo.com
|
||||
username: root
|
||||
password: 1234
|
||||
port: 22
|
||||
script:
|
||||
- echo hello
|
||||
- echo world
|
||||
+ when:
|
||||
+ event: tag
|
||||
```
|
||||
|
||||
Example configuration using password from secrets:
|
||||
|
||||
```diff
|
||||
|
||||
@@ -21,6 +21,11 @@ func main() {
|
||||
Version = fmt.Sprintf("1.3.1+%s", build)
|
||||
}
|
||||
|
||||
// Load env-file if it exists first
|
||||
if filename, found := os.LookupEnv("PLUGIN_ENV_FILE"); found {
|
||||
_ = godotenv.Load(filename)
|
||||
}
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "Drone SSH"
|
||||
app.Usage = "Executing remote ssh commands"
|
||||
@@ -87,9 +92,10 @@ func main() {
|
||||
Usage: "execute commands",
|
||||
EnvVar: "PLUGIN_SCRIPT,SSH_SCRIPT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "env-file",
|
||||
Usage: "source env file",
|
||||
cli.BoolFlag{
|
||||
Name: "script.stop",
|
||||
Usage: "stop script after first failure",
|
||||
EnvVar: "PLUGIN_SCRIPT_STOP",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "proxy.ssh-key",
|
||||
@@ -185,10 +191,6 @@ REPOSITORY:
|
||||
}
|
||||
|
||||
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"),
|
||||
@@ -200,6 +202,7 @@ func run(c *cli.Context) error {
|
||||
Timeout: c.Duration("timeout"),
|
||||
CommandTimeout: c.Int("command.timeout"),
|
||||
Script: c.StringSlice("script"),
|
||||
ScriptStop: c.Bool("script.stop"),
|
||||
Secrets: c.StringSlice("secrets"),
|
||||
Envs: c.StringSlice("envs"),
|
||||
Debug: c.Bool("debug"),
|
||||
@@ -214,6 +217,7 @@ func run(c *cli.Context) error {
|
||||
Timeout: c.Duration("proxy.timeout"),
|
||||
},
|
||||
},
|
||||
Writer: os.Stdout,
|
||||
}
|
||||
|
||||
return plugin.Exec()
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -30,6 +31,7 @@ type (
|
||||
Timeout time.Duration
|
||||
CommandTimeout int
|
||||
Script []string
|
||||
ScriptStop bool
|
||||
Secrets []string
|
||||
Envs []string
|
||||
Proxy easyssh.DefaultConfig
|
||||
@@ -40,9 +42,14 @@ type (
|
||||
// Plugin structure
|
||||
Plugin struct {
|
||||
Config Config
|
||||
Writer io.Writer
|
||||
}
|
||||
)
|
||||
|
||||
func escapeArg(arg string) string {
|
||||
return "'" + strings.Replace(arg, "'", `'\''`, -1) + "'"
|
||||
}
|
||||
|
||||
func (p Plugin) exec(host string, wg *sync.WaitGroup, errChannel chan error) {
|
||||
// Create MakeConfig instance with remote username, server address and path to private key.
|
||||
ssh := &easyssh.MakeConfig{
|
||||
@@ -71,12 +78,12 @@ func (p Plugin) exec(host string, wg *sync.WaitGroup, errChannel chan error) {
|
||||
env := []string{}
|
||||
for _, key := range p.Config.Envs {
|
||||
key = strings.ToUpper(key)
|
||||
val := os.Getenv(key)
|
||||
val = strings.Replace(val, " ", "", -1)
|
||||
env = append(env, key+"='"+val+"'")
|
||||
if val, found := os.LookupEnv(key); found {
|
||||
env = append(env, key+"="+escapeArg(val))
|
||||
}
|
||||
}
|
||||
|
||||
p.Config.Script = append(env, p.Config.Script...)
|
||||
p.Config.Script = append(env, p.scriptCommands()...)
|
||||
|
||||
if p.Config.Debug {
|
||||
p.log(host, "======ENV======")
|
||||
@@ -118,10 +125,13 @@ func (p Plugin) exec(host string, wg *sync.WaitGroup, errChannel chan error) {
|
||||
}
|
||||
|
||||
func (p Plugin) log(host string, message ...interface{}) {
|
||||
if p.Writer == nil {
|
||||
p.Writer = os.Stdout
|
||||
}
|
||||
if count := len(p.Config.Host); count == 1 {
|
||||
fmt.Printf("%s", fmt.Sprintln(message...))
|
||||
fmt.Fprintf(p.Writer, "%s", fmt.Sprintln(message...))
|
||||
} else {
|
||||
fmt.Printf("%s: %s", host, fmt.Sprintln(message...))
|
||||
fmt.Fprintf(p.Writer, "%s: %s", host, fmt.Sprintln(message...))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,3 +180,22 @@ func (p Plugin) Exec() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Plugin) scriptCommands() []string {
|
||||
numCommands := len(p.Config.Script)
|
||||
if p.Config.ScriptStop {
|
||||
numCommands *= 2
|
||||
}
|
||||
|
||||
commands := make([]string, numCommands)
|
||||
|
||||
for _, cmd := range p.Config.Script {
|
||||
if p.Config.ScriptStop {
|
||||
commands = append(commands, "DRONE_SSH_PREV_COMMAND_EXIT_CODE=$? ; if [ $DRONE_SSH_PREV_COMMAND_EXIT_CODE -ne 0 ]; then exit $DRONE_SSH_PREV_COMMAND_EXIT_CODE; fi;")
|
||||
}
|
||||
|
||||
commands = append(commands, cmd)
|
||||
}
|
||||
|
||||
return commands
|
||||
}
|
||||
|
||||
+256
-1
@@ -1,7 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/appleboy/easyssh-proxy"
|
||||
@@ -23,6 +25,7 @@ func TestMissingKeyOrPassword(t *testing.T) {
|
||||
Host: []string{"localhost"},
|
||||
UserName: "ubuntu",
|
||||
},
|
||||
os.Stdout,
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
@@ -39,6 +42,7 @@ func TestSetPasswordAndKey(t *testing.T) {
|
||||
Password: "1234",
|
||||
Key: "1234",
|
||||
},
|
||||
os.Stdout,
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
@@ -232,7 +236,7 @@ func TestSSHCommandExitCodeError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetENV(t *testing.T) {
|
||||
os.Setenv("FOO", "1)")
|
||||
os.Setenv("FOO", `' 1) '`)
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost"},
|
||||
@@ -257,6 +261,33 @@ func TestSetENV(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestSetExistingENV(t *testing.T) {
|
||||
os.Setenv("FOO", "Value for foo")
|
||||
os.Setenv("BAR", "")
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost"},
|
||||
UserName: "drone-scp",
|
||||
Port: 22,
|
||||
KeyPath: "./tests/.ssh/id_rsa",
|
||||
Secrets: []string{"FOO"},
|
||||
Envs: []string{"foo", "bar", "baz"},
|
||||
Debug: true,
|
||||
Script: []string{"export FOO", "export BAR", "export BAZ", "env | grep -q '^FOO=Value for foo$'", "env | grep -q '^BAR=$'", "if env | grep -q BAZ; then false; else true; fi"},
|
||||
CommandTimeout: 1,
|
||||
Proxy: easyssh.DefaultConfig{
|
||||
Server: "localhost",
|
||||
User: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "./tests/.ssh/id_rsa",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestSyncMode(t *testing.T) {
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
@@ -273,3 +304,227 @@ func TestSyncMode(t *testing.T) {
|
||||
err := plugin.Exec()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func Test_escapeArg(t *testing.T) {
|
||||
type args struct {
|
||||
arg string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "escape nothing",
|
||||
args: args{
|
||||
arg: "Hi I am appleboy",
|
||||
},
|
||||
want: `'Hi I am appleboy'`,
|
||||
},
|
||||
{
|
||||
name: "escape single quote",
|
||||
args: args{
|
||||
arg: "Hi I am 'appleboy'",
|
||||
},
|
||||
want: `'Hi I am '\''appleboy'\'''`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := escapeArg(tt.args.arg)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandOutput(t *testing.T) {
|
||||
var (
|
||||
buffer bytes.Buffer
|
||||
expected = `
|
||||
localhost: ======CMD======
|
||||
localhost: pwd
|
||||
whoami
|
||||
uname
|
||||
localhost: ======END======
|
||||
localhost: out: /home/drone-scp
|
||||
localhost: out: drone-scp
|
||||
localhost: out: Linux
|
||||
127.0.0.1: ======CMD======
|
||||
127.0.0.1: pwd
|
||||
whoami
|
||||
uname
|
||||
127.0.0.1: ======END======
|
||||
127.0.0.1: out: /home/drone-scp
|
||||
127.0.0.1: out: drone-scp
|
||||
127.0.0.1: out: Linux
|
||||
`
|
||||
)
|
||||
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost", "127.0.0.1"},
|
||||
UserName: "drone-scp",
|
||||
Port: 22,
|
||||
KeyPath: "./tests/.ssh/id_rsa",
|
||||
Script: []string{
|
||||
"pwd",
|
||||
"whoami",
|
||||
"uname",
|
||||
},
|
||||
CommandTimeout: 60,
|
||||
Sync: true,
|
||||
},
|
||||
Writer: &buffer,
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, unindent(expected), unindent(buffer.String()))
|
||||
}
|
||||
|
||||
func TestScriptStop(t *testing.T) {
|
||||
var (
|
||||
buffer bytes.Buffer
|
||||
expected = `
|
||||
======CMD======
|
||||
mkdir a/b/c
|
||||
mkdir d/e/f
|
||||
======END======
|
||||
err: mkdir: can't create directory 'a/b/c': No such file or directory
|
||||
`
|
||||
)
|
||||
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost"},
|
||||
UserName: "drone-scp",
|
||||
Port: 22,
|
||||
KeyPath: "./tests/.ssh/id_rsa",
|
||||
Script: []string{
|
||||
"mkdir a/b/c",
|
||||
"mkdir d/e/f",
|
||||
},
|
||||
CommandTimeout: 10,
|
||||
ScriptStop: true,
|
||||
},
|
||||
Writer: &buffer,
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
assert.NotNil(t, err)
|
||||
|
||||
assert.Equal(t, unindent(expected), unindent(buffer.String()))
|
||||
}
|
||||
|
||||
func TestNoneScriptStop(t *testing.T) {
|
||||
var (
|
||||
buffer bytes.Buffer
|
||||
expected = `
|
||||
======CMD======
|
||||
mkdir a/b/c
|
||||
mkdir d/e/f
|
||||
======END======
|
||||
err: mkdir: can't create directory 'a/b/c': No such file or directory
|
||||
err: mkdir: can't create directory 'd/e/f': No such file or directory
|
||||
`
|
||||
)
|
||||
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost"},
|
||||
UserName: "drone-scp",
|
||||
Port: 22,
|
||||
KeyPath: "./tests/.ssh/id_rsa",
|
||||
Script: []string{
|
||||
"mkdir a/b/c",
|
||||
"mkdir d/e/f",
|
||||
},
|
||||
CommandTimeout: 10,
|
||||
},
|
||||
Writer: &buffer,
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
assert.NotNil(t, err)
|
||||
|
||||
assert.Equal(t, unindent(expected), unindent(buffer.String()))
|
||||
}
|
||||
|
||||
func TestEnvOutput(t *testing.T) {
|
||||
var (
|
||||
buffer bytes.Buffer
|
||||
expected = `
|
||||
======CMD======
|
||||
echo "[${ENV_1}]"
|
||||
echo "[${ENV_2}]"
|
||||
echo "[${ENV_3}]"
|
||||
echo "[${ENV_4}]"
|
||||
echo "[${ENV_5}]"
|
||||
echo "[${ENV_6}]"
|
||||
echo "[${ENV_7}]"
|
||||
======END======
|
||||
======ENV======
|
||||
ENV_1='test'
|
||||
ENV_2='test test'
|
||||
ENV_3='test '
|
||||
ENV_4=' test test '
|
||||
ENV_5='test'\'''
|
||||
ENV_6='test"'
|
||||
ENV_7='test,!#;?.@$~'\''"'
|
||||
======END======
|
||||
out: [test]
|
||||
out: [test test]
|
||||
out: [test ]
|
||||
out: [ test test ]
|
||||
out: [test']
|
||||
out: [test"]
|
||||
out: [test,!#;?.@$~'"]
|
||||
`
|
||||
)
|
||||
|
||||
os.Setenv("ENV_1", `test`)
|
||||
os.Setenv("ENV_2", `test test`)
|
||||
os.Setenv("ENV_3", `test `)
|
||||
os.Setenv("ENV_4", ` test test `)
|
||||
os.Setenv("ENV_5", `test'`)
|
||||
os.Setenv("ENV_6", `test"`)
|
||||
os.Setenv("ENV_7", `test,!#;?.@$~'"`)
|
||||
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost"},
|
||||
UserName: "drone-scp",
|
||||
Port: 22,
|
||||
KeyPath: "./tests/.ssh/id_rsa",
|
||||
Envs: []string{"env_1", "env_2", "env_3", "env_4", "env_5", "env_6", "env_7"},
|
||||
Debug: true,
|
||||
Script: []string{
|
||||
`echo "[${ENV_1}]"`,
|
||||
`echo "[${ENV_2}]"`,
|
||||
`echo "[${ENV_3}]"`,
|
||||
`echo "[${ENV_4}]"`,
|
||||
`echo "[${ENV_5}]"`,
|
||||
`echo "[${ENV_6}]"`,
|
||||
`echo "[${ENV_7}]"`,
|
||||
},
|
||||
CommandTimeout: 10,
|
||||
Proxy: easyssh.DefaultConfig{
|
||||
Server: "localhost",
|
||||
User: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "./tests/.ssh/id_rsa",
|
||||
},
|
||||
},
|
||||
Writer: &buffer,
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, unindent(expected), unindent(buffer.String()))
|
||||
}
|
||||
|
||||
func unindent(text string) string {
|
||||
return strings.TrimSpace(strings.Replace(text, "\t", "", -1))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user