mirror of
https://github.com/appleboy/drone-ssh.git
synced 2026-06-16 14:49:25 +08:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9cc37282c | |||
| 6e431b0c53 | |||
| 3499506089 | |||
| 6c0b475c15 | |||
| 60993a71e2 | |||
| 8bfc58f9d0 | |||
| 7f4cb1c1d0 | |||
| f92f762c9d | |||
| 84cb184039 | |||
| 31c084fd3e | |||
| 69b3a40978 | |||
| 4d443c40f2 | |||
| 9dd4b8db8d | |||
| 45f43d7ffd | |||
| 7220c94832 | |||
| 2d5668ff17 | |||
| 6f1ace35bf | |||
| 05ebe5b663 | |||
| e331f975ad | |||
| f943ff7179 | |||
| 65e15c4aab | |||
| 83273b5669 | |||
| a8392b5f22 | |||
| e057a699a4 | |||
| 14fddbbba5 | |||
| 5fbd22f265 | |||
| bf269615ce | |||
| 538a5a6ce5 | |||
| 78f4f15754 | |||
| 40323f23e5 | |||
| ed83305de8 | |||
| 4e625fa760 | |||
| c79b44dca2 | |||
| c86c472904 | |||
| ecfaecd46d | |||
| e6d4fa77d1 | |||
| 9651a4eb6c | |||
| b5b13e8b72 | |||
| 26b3d47ee2 | |||
| 0a78278313 | |||
| a7c37e0936 | |||
| 699d9148d8 | |||
| ceec42efdd | |||
| 88b5394dac | |||
| 1637772e0b | |||
| efdac217bd | |||
| f81056261d | |||
| 3fffe80a14 | |||
| 2d568d1fde | |||
| f26bd7f7f7 | |||
| 95427edbba | |||
| 7f168bd1cb | |||
| b6c973ef1e | |||
| 356b2ae6cc | |||
| b698d56d60 | |||
| 06f4f77ebc | |||
| b63f275e9e |
+64
-36
@@ -1,68 +1,96 @@
|
|||||||
workspace:
|
workspace:
|
||||||
base: /srv/app
|
base: /go/src
|
||||||
path: src/github.com/appleboy/drone-ssh
|
path: github.com/appleboy/drone-ssh
|
||||||
|
|
||||||
|
clone:
|
||||||
|
git:
|
||||||
|
image: plugins/git
|
||||||
|
depth: 50
|
||||||
|
tags: true
|
||||||
|
|
||||||
pipeline:
|
pipeline:
|
||||||
clone:
|
lint:
|
||||||
image: plugins/git
|
image: golang:1.11
|
||||||
tags: true
|
pull: true
|
||||||
|
group: golang
|
||||||
|
commands:
|
||||||
|
- make vet
|
||||||
|
- make lint
|
||||||
|
- make test-vendor
|
||||||
|
|
||||||
|
linux_amd64:
|
||||||
|
image: golang:1.11
|
||||||
|
pull: true
|
||||||
|
group: golang
|
||||||
|
commands:
|
||||||
|
- make linux_amd64
|
||||||
|
|
||||||
|
linux_arm64:
|
||||||
|
image: golang:1.11
|
||||||
|
pull: true
|
||||||
|
group: golang
|
||||||
|
commands:
|
||||||
|
- make linux_arm64
|
||||||
|
|
||||||
|
linux_arm:
|
||||||
|
image: golang:1.11
|
||||||
|
pull: true
|
||||||
|
group: golang
|
||||||
|
commands:
|
||||||
|
- make linux_arm
|
||||||
|
|
||||||
test:
|
test:
|
||||||
image: appleboy/golang-testing
|
image: appleboy/golang-testing
|
||||||
pull: true
|
pull: true
|
||||||
environment:
|
group: golang
|
||||||
TAGS: netgo
|
|
||||||
GOPATH: /srv/app
|
|
||||||
commands:
|
commands:
|
||||||
- make ssh-server
|
- make ssh-server
|
||||||
- make vet
|
- make test
|
||||||
- make lint
|
|
||||||
# - make test
|
|
||||||
- coverage all
|
|
||||||
- make coverage
|
- make coverage
|
||||||
- make build
|
|
||||||
# build binary for docker image
|
|
||||||
- make static_build
|
|
||||||
when:
|
|
||||||
event: [ push, tag, pull_request ]
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
image: appleboy/golang-testing
|
image: golang:1.11
|
||||||
pull: true
|
pull: true
|
||||||
environment:
|
|
||||||
TAGS: netgo
|
|
||||||
GOPATH: /srv/app
|
|
||||||
commands:
|
commands:
|
||||||
- make release
|
- make release
|
||||||
when:
|
when:
|
||||||
event: [ tag ]
|
event: [ tag ]
|
||||||
branch: [ refs/tags/* ]
|
|
||||||
local: false
|
local: false
|
||||||
|
|
||||||
publish_tag:
|
codecov:
|
||||||
image: plugins/docker
|
image: robertstettner/drone-codecov
|
||||||
repo: ${DRONE_REPO}
|
secrets: [ codecov_token ]
|
||||||
tags: [ '${DRONE_TAG}' ]
|
files:
|
||||||
|
- .cover/coverage.txt
|
||||||
when:
|
when:
|
||||||
event: [ tag ]
|
event: [ push, pull_request ]
|
||||||
branch: [ refs/tags/* ]
|
status: [ success ]
|
||||||
local: false
|
|
||||||
|
|
||||||
publish_latest:
|
publish:
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
|
pull: true
|
||||||
repo: ${DRONE_REPO}
|
repo: ${DRONE_REPO}
|
||||||
tags: [ 'latest' ]
|
default_tags: true
|
||||||
|
secrets: [ docker_username, docker_password ]
|
||||||
|
group: release
|
||||||
when:
|
when:
|
||||||
event: [ push ]
|
event: [ push, tag ]
|
||||||
branch: [ master ]
|
|
||||||
local: false
|
local: false
|
||||||
|
|
||||||
release:
|
release_tag:
|
||||||
image: plugins/github-release
|
image: plugins/github-release
|
||||||
api_key: ${GITHUB_RELEASE_API_KEY}
|
pull: true
|
||||||
|
secrets: [ github_release_api_key ]
|
||||||
|
group: release
|
||||||
files:
|
files:
|
||||||
- dist/release/*
|
- dist/release/*
|
||||||
when:
|
when:
|
||||||
event: [ tag ]
|
event: [ tag ]
|
||||||
branch: [ refs/tags/* ]
|
|
||||||
local: false
|
local: false
|
||||||
|
|
||||||
|
discord:
|
||||||
|
image: appleboy/drone-discord
|
||||||
|
pull: true
|
||||||
|
secrets: [ discord_webhook_id, discord_webhook_token ]
|
||||||
|
when:
|
||||||
|
status: [ changed, failure ]
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# unifying the coding style for different editors and IDEs => editorconfig.org
|
||||||
|
|
||||||
|
; indicate this is the root of the project
|
||||||
|
root = true
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
; common
|
||||||
|
###########################################################
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
end_of_line = LF
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
; make
|
||||||
|
###########################################################
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
; markdown
|
||||||
|
###########################################################
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
; golang
|
||||||
|
###########################################################
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
@@ -25,5 +25,6 @@ _testmain.go
|
|||||||
.env
|
.env
|
||||||
|
|
||||||
coverage.txt
|
coverage.txt
|
||||||
|
release
|
||||||
drone-ssh
|
drone-ssh
|
||||||
.cover
|
.cover
|
||||||
|
|||||||
@@ -40,38 +40,6 @@ pipeline:
|
|||||||
- echo world
|
- echo world
|
||||||
```
|
```
|
||||||
|
|
||||||
Example configuration for login with user private key:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
pipeline:
|
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
|
||||||
host: foo.com
|
|
||||||
username: root
|
|
||||||
- password: 1234
|
|
||||||
+ key: ${DEPLOY_KEY}
|
|
||||||
port: 22
|
|
||||||
script:
|
|
||||||
- echo hello
|
|
||||||
- echo world
|
|
||||||
```
|
|
||||||
|
|
||||||
Example configuration for login with file path of user private key:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
pipeline:
|
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
|
||||||
host: foo.com
|
|
||||||
username: root
|
|
||||||
- password: 1234
|
|
||||||
+ key_path: ./deploy/key.pem
|
|
||||||
port: 22
|
|
||||||
script:
|
|
||||||
- echo hello
|
|
||||||
- echo world
|
|
||||||
```
|
|
||||||
|
|
||||||
Example configuration for command timeout (unit: second), default value is 60 seconds:
|
Example configuration for command timeout (unit: second), default value is 60 seconds:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
@@ -82,7 +50,7 @@ pipeline:
|
|||||||
username: root
|
username: root
|
||||||
password: 1234
|
password: 1234
|
||||||
port: 22
|
port: 22
|
||||||
+ command_timeout: 10
|
+ command_timeout: 120
|
||||||
script:
|
script:
|
||||||
- echo hello
|
- echo hello
|
||||||
- echo world
|
- echo world
|
||||||
@@ -96,18 +64,49 @@ pipeline:
|
|||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
host: foo.com
|
host: foo.com
|
||||||
username: root
|
username: root
|
||||||
|
password: 1234
|
||||||
port: 22
|
port: 22
|
||||||
key: ${DEPLOY_KEY}
|
|
||||||
script:
|
script:
|
||||||
- echo hello
|
- echo hello
|
||||||
- echo world
|
- echo world
|
||||||
+ proxy_host: 10.130.33.145
|
+ proxy_host: 10.130.33.145
|
||||||
+ proxy_user: ubuntu
|
+ proxy_user: ubuntu
|
||||||
+ proxy_port: 22
|
+ proxy_port: 22
|
||||||
+ proxy_key: ${PROXY_KEY}
|
+ proxy_password: 1234
|
||||||
```
|
```
|
||||||
|
|
||||||
Example configuration for success build:
|
Example configuration using password from secrets:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
pipeline:
|
||||||
|
ssh:
|
||||||
|
image: appleboy/drone-ssh
|
||||||
|
host: foo.com
|
||||||
|
username: root
|
||||||
|
- password: 1234
|
||||||
|
port: 22
|
||||||
|
+ secrets: [ ssh_password ]
|
||||||
|
script:
|
||||||
|
- echo hello
|
||||||
|
- echo world
|
||||||
|
```
|
||||||
|
|
||||||
|
Example configuration using ssh key from secrets:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
pipeline:
|
||||||
|
ssh:
|
||||||
|
image: appleboy/drone-ssh
|
||||||
|
host: foo.com
|
||||||
|
username: root
|
||||||
|
port: 22
|
||||||
|
+ secrets: [ ssh_key ]
|
||||||
|
script:
|
||||||
|
- echo hello
|
||||||
|
- echo world
|
||||||
|
```
|
||||||
|
|
||||||
|
Example configuration for exporting custom secrets:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
pipeline:
|
pipeline:
|
||||||
@@ -117,30 +116,31 @@ pipeline:
|
|||||||
username: root
|
username: root
|
||||||
password: 1234
|
password: 1234
|
||||||
port: 22
|
port: 22
|
||||||
|
+ secrets: [ aws_access_key_id ]
|
||||||
|
+ envs: [ aws_access_key_id ]
|
||||||
script:
|
script:
|
||||||
- echo hello
|
- export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
|
||||||
- echo world
|
|
||||||
+ when:
|
|
||||||
+ status: success
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Example configuration for tag event:
|
# Secret Reference
|
||||||
|
|
||||||
```diff
|
ssh_username
|
||||||
pipeline:
|
: account for target host user
|
||||||
ssh:
|
|
||||||
image: appleboy/drone-ssh
|
ssh_password
|
||||||
host: foo.com
|
: password for target host user
|
||||||
username: root
|
|
||||||
password: 1234
|
ssh_key
|
||||||
port: 22
|
: plain text of user private key
|
||||||
script:
|
|
||||||
- echo hello
|
proxy_ssh_username
|
||||||
- echo world
|
: account for user of proxy server
|
||||||
+ when:
|
|
||||||
+ status: success
|
proxy_ssh_password
|
||||||
+ event: tag
|
: password for user of proxy server
|
||||||
```
|
|
||||||
|
proxy_ssh_key
|
||||||
|
: plain text of user private key for proxy server
|
||||||
|
|
||||||
# Parameter Reference
|
# Parameter Reference
|
||||||
|
|
||||||
@@ -162,6 +162,9 @@ key
|
|||||||
key_path
|
key_path
|
||||||
: key path of user private key
|
: key path of user private key
|
||||||
|
|
||||||
|
envs
|
||||||
|
: custom secrets which are made available in the script section
|
||||||
|
|
||||||
script
|
script
|
||||||
: execute commands on a remote server
|
: execute commands on a remote server
|
||||||
|
|
||||||
|
|||||||
+10
-4
@@ -1,10 +1,16 @@
|
|||||||
FROM alpine:3.4
|
FROM alpine:3.4
|
||||||
|
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk add \
|
apk add -U --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
openssh-client && \
|
openssh-client && \
|
||||||
rm -rf /var/cache/apk/*
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
ADD drone-ssh /bin/
|
LABEL org.label-schema.version=latest
|
||||||
|
LABEL org.label-schema.vcs-url="https://github.com/appleboy/drone-ssh.git"
|
||||||
|
LABEL org.label-schema.name="drone-ssh"
|
||||||
|
LABEL org.label-schema.vendor="Bo-Yi Wu"
|
||||||
|
LABEL org.label-schema.schema-version="1.0"
|
||||||
|
|
||||||
|
ADD release/linux/amd64/drone-ssh /bin/
|
||||||
ENTRYPOINT ["/bin/drone-ssh"]
|
ENTRYPOINT ["/bin/drone-ssh"]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
DIST := dist
|
DIST := dist
|
||||||
EXECUTABLE := drone-ssh
|
EXECUTABLE := drone-ssh
|
||||||
|
GO ?= go
|
||||||
|
|
||||||
# for dockerhub
|
# for dockerhub
|
||||||
DEPLOY_ACCOUNT := appleboy
|
DEPLOY_ACCOUNT := appleboy
|
||||||
@@ -9,11 +10,12 @@ DEPLOY_IMAGE := $(EXECUTABLE)
|
|||||||
GOFMT ?= gofmt "-s"
|
GOFMT ?= gofmt "-s"
|
||||||
|
|
||||||
TARGETS ?= linux darwin windows
|
TARGETS ?= linux darwin windows
|
||||||
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
|
PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/)
|
||||||
GOFILES := find . -name "*.go" -type f -not -path "./vendor/*"
|
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
|
||||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||||
TAGS ?=
|
TAGS ?=
|
||||||
LDFLAGS ?= -X 'main.Version=$(VERSION)'
|
LDFLAGS ?= -X 'main.Version=$(VERSION)' -X 'main.build=$(NUMBER)'
|
||||||
|
TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'tempdir')
|
||||||
|
|
||||||
ifneq ($(shell uname), Darwin)
|
ifneq ($(shell uname), Darwin)
|
||||||
EXTLDFLAGS = -extldflags "-static" $(null)
|
EXTLDFLAGS = -extldflags "-static" $(null)
|
||||||
@@ -23,6 +25,10 @@ endif
|
|||||||
|
|
||||||
ifneq ($(DRONE_TAG),)
|
ifneq ($(DRONE_TAG),)
|
||||||
VERSION ?= $(DRONE_TAG)
|
VERSION ?= $(DRONE_TAG)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq ($(DRONE_BUILD_NUMBER),)
|
||||||
|
NUMBER ?= $(DRONE_BUILD_NUMBER)
|
||||||
else
|
else
|
||||||
VERSION ?= $(shell git describe --tags --always || git rev-parse --short HEAD)
|
VERSION ?= $(shell git describe --tags --always || git rev-parse --short HEAD)
|
||||||
endif
|
endif
|
||||||
@@ -31,50 +37,63 @@ all: build
|
|||||||
|
|
||||||
.PHONY: fmt-check
|
.PHONY: fmt-check
|
||||||
fmt-check:
|
fmt-check:
|
||||||
# get all go files and run go fmt on them
|
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||||
@files=$$($(GOFILES) | xargs $(GOFMT) -l); if [ -n "$$files" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
echo "Please run 'make fmt' and commit the result:"; \
|
echo "Please run 'make fmt' and commit the result:"; \
|
||||||
echo "$${files}"; \
|
echo "$${diff}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
|
.PHONY: test-vendor
|
||||||
|
test-vendor:
|
||||||
|
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) get -u github.com/kardianos/govendor; \
|
||||||
|
fi
|
||||||
|
govendor list +unused | tee "$(TMPDIR)/wc-gitea-unused"
|
||||||
|
[ $$(cat "$(TMPDIR)/wc-gitea-unused" | wc -l) -eq 0 ] || echo "Warning: /!\\ Some vendor are not used /!\\"
|
||||||
|
|
||||||
|
govendor list +outside | tee "$(TMPDIR)/wc-gitea-outside"
|
||||||
|
[ $$(cat "$(TMPDIR)/wc-gitea-outside" | wc -l) -eq 0 ] || exit 1
|
||||||
|
|
||||||
|
govendor status || exit 1
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
$(GOFILES) | xargs $(GOFMT) -w
|
$(GOFMT) -w $(GOFILES)
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet $(PACKAGES)
|
$(GO) vet $(PACKAGES)
|
||||||
|
|
||||||
errcheck:
|
errcheck:
|
||||||
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
go get -u github.com/kisielk/errcheck; \
|
$(GO) get -u github.com/kisielk/errcheck; \
|
||||||
fi
|
fi
|
||||||
errcheck $(PACKAGES)
|
errcheck $(PACKAGES)
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
go get -u github.com/golang/lint/golint; \
|
$(GO) get -u github.com/golang/lint/golint; \
|
||||||
fi
|
fi
|
||||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||||
|
|
||||||
unconvert:
|
unconvert:
|
||||||
@hash unconvert > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash unconvert > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
go get -u github.com/mdempsky/unconvert; \
|
$(GO) get -u github.com/mdempsky/unconvert; \
|
||||||
fi
|
fi
|
||||||
for PKG in $(PACKAGES); do unconvert -v $$PKG || exit 1; done;
|
for PKG in $(PACKAGES); do unconvert -v $$PKG || exit 1; done;
|
||||||
|
|
||||||
test: fmt-check
|
test: fmt-check
|
||||||
for PKG in $(PACKAGES); do go test -v -cover -coverprofile $$GOPATH/src/$$PKG/coverage.txt $$PKG || exit 1; done;
|
for PKG in $(PACKAGES); do $(GO) test -v -cover -coverprofile $$GOPATH/src/$$PKG/coverage.txt $$PKG || exit 1; done;
|
||||||
|
|
||||||
html:
|
html:
|
||||||
go tool cover -html=coverage.txt
|
$(GO) tool cover -html=coverage.txt
|
||||||
|
|
||||||
install: $(SOURCES)
|
install: $(SOURCES)
|
||||||
go install -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)'
|
$(GO) install -v -tags '$(TAGS)' -ldflags "$(EXTLDFLAGS)-s -w $(LDFLAGS)"
|
||||||
|
|
||||||
build: $(EXECUTABLE)
|
build: $(EXECUTABLE)
|
||||||
|
|
||||||
$(EXECUTABLE): $(SOURCES)
|
$(EXECUTABLE): $(SOURCES)
|
||||||
go build -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o $@
|
$(GO) build -v -tags '$(TAGS)' -ldflags "$(EXTLDFLAGS)-s -w $(LDFLAGS)" -o $@
|
||||||
|
|
||||||
release: release-dirs release-build release-copy release-check
|
release: release-dirs release-build release-copy release-check
|
||||||
|
|
||||||
@@ -83,7 +102,7 @@ release-dirs:
|
|||||||
|
|
||||||
release-build:
|
release-build:
|
||||||
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
go get -u github.com/mitchellh/gox; \
|
$(GO) get -u github.com/mitchellh/gox; \
|
||||||
fi
|
fi
|
||||||
gox -os="$(TARGETS)" -arch="amd64 386" -tags="$(TAGS)" -ldflags="-s -w $(LDFLAGS)" -output="$(DIST)/binaries/$(EXECUTABLE)-$(VERSION)-{{.OS}}-{{.Arch}}"
|
gox -os="$(TARGETS)" -arch="amd64 386" -tags="$(TAGS)" -ldflags="-s -w $(LDFLAGS)" -output="$(DIST)/binaries/$(EXECUTABLE)-$(VERSION)-{{.OS}}-{{.Arch}}"
|
||||||
|
|
||||||
@@ -93,15 +112,18 @@ release-copy:
|
|||||||
release-check:
|
release-check:
|
||||||
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
|
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
|
||||||
|
|
||||||
# for docker.
|
linux_amd64:
|
||||||
static_build:
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -a -tags '$(TAGS)' -ldflags "$(EXTLDFLAGS)-s -w $(LDFLAGS)" -o release/linux/amd64/$(EXECUTABLE)
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o $(DEPLOY_IMAGE)
|
|
||||||
|
linux_arm64:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -a -tags '$(TAGS)' -ldflags "$(EXTLDFLAGS)-s -w $(LDFLAGS)" -o release/linux/arm64/$(EXECUTABLE)
|
||||||
|
|
||||||
|
linux_arm:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 $(GO) build -a -tags '$(TAGS)' -ldflags "$(EXTLDFLAGS)-s -w $(LDFLAGS)" -o release/arm/amd64/$(EXECUTABLE)
|
||||||
|
|
||||||
docker_image:
|
docker_image:
|
||||||
docker build -t $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE) .
|
docker build -t $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE) .
|
||||||
|
|
||||||
docker: static_build docker_image
|
|
||||||
|
|
||||||
docker_deploy:
|
docker_deploy:
|
||||||
ifeq ($(tag),)
|
ifeq ($(tag),)
|
||||||
@echo "Usage: make $@ tag=<tag>"
|
@echo "Usage: make $@ tag=<tag>"
|
||||||
@@ -112,13 +134,10 @@ endif
|
|||||||
docker push $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag)
|
docker push $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag)
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
sed -i '/main.go/d' .cover/coverage.txt
|
sed -i '/main.go/d' coverage.txt
|
||||||
curl -s https://codecov.io/bash > .codecov && \
|
|
||||||
chmod +x .codecov && \
|
|
||||||
./.codecov -f .cover/coverage.txt
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
go clean -x -i ./...
|
$(GO) clean -x -i ./...
|
||||||
rm -rf coverage.txt $(EXECUTABLE) $(DIST) vendor
|
rm -rf coverage.txt $(EXECUTABLE) $(DIST) vendor
|
||||||
|
|
||||||
ssh-server:
|
ssh-server:
|
||||||
@@ -133,5 +152,10 @@ ssh-server:
|
|||||||
rm -rf /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_dsa_key
|
rm -rf /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_dsa_key
|
||||||
./tests/entrypoint.sh /usr/sbin/sshd -D &
|
./tests/entrypoint.sh /usr/sbin/sshd -D &
|
||||||
|
|
||||||
|
# Show source statistics.
|
||||||
|
cloc:
|
||||||
|
@cloc -exclude-dir=vendor,node_modules .
|
||||||
|
.PHONY: cloc
|
||||||
|
|
||||||
version:
|
version:
|
||||||
@echo $(VERSION)
|
@echo $(VERSION)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ information and a listing of the available options please take a look at [the do
|
|||||||
|
|
||||||
**Note: Please update your image config path to `appleboy/drone-ssh` for drone. `plugins/ssh` is no longer maintained.**
|
**Note: Please update your image config path to `appleboy/drone-ssh` for drone. `plugins/ssh` is no longer maintained.**
|
||||||
|
|
||||||
|

|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
Build the binary with the following commands:
|
Build the binary with the following commands:
|
||||||
@@ -49,3 +50,26 @@ docker run --rm \
|
|||||||
-w $(pwd) \
|
-w $(pwd) \
|
||||||
appleboy/drone-ssh
|
appleboy/drone-ssh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Mount key from file path
|
||||||
|
|
||||||
|
Please make sure that enable the `trusted` mode in project setting.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Mount private key in `volumes` setting of `.drone.yml` config
|
||||||
|
|
||||||
|
```diff
|
||||||
|
pipeline:
|
||||||
|
ssh:
|
||||||
|
image: appleboy/drone-ssh
|
||||||
|
host: xxxxx.com
|
||||||
|
username: deploy
|
||||||
|
+ volumes:
|
||||||
|
+ - /root/drone_rsa:/root/ssh/drone_rsa
|
||||||
|
key_path: /root/ssh/drone_rsa
|
||||||
|
script:
|
||||||
|
- echo "test ssh"
|
||||||
|
```
|
||||||
|
|
||||||
|
See the detail of [issue comment](https://github.com/appleboy/drone-ssh/issues/51#issuecomment-336732928).
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/appleboy/easyssh-proxy"
|
"github.com/appleboy/easyssh-proxy"
|
||||||
@@ -9,10 +10,22 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// build number set at compile-time
|
||||||
|
var build = "0"
|
||||||
|
|
||||||
// Version set at compile-time
|
// Version set at compile-time
|
||||||
var Version = "v1.1.0-dev"
|
var Version string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if Version == "" {
|
||||||
|
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 := cli.NewApp()
|
||||||
app.Name = "Drone SSH"
|
app.Name = "Drone SSH"
|
||||||
app.Usage = "Executing remote ssh commands"
|
app.Usage = "Executing remote ssh commands"
|
||||||
@@ -58,6 +71,11 @@ func main() {
|
|||||||
EnvVar: "PLUGIN_PORT,SSH_PORT",
|
EnvVar: "PLUGIN_PORT,SSH_PORT",
|
||||||
Value: 22,
|
Value: 22,
|
||||||
},
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "sync",
|
||||||
|
Usage: "sync mode",
|
||||||
|
EnvVar: "PLUGIN_SYNC",
|
||||||
|
},
|
||||||
cli.DurationFlag{
|
cli.DurationFlag{
|
||||||
Name: "timeout,t",
|
Name: "timeout,t",
|
||||||
Usage: "connection timeout",
|
Usage: "connection timeout",
|
||||||
@@ -74,9 +92,10 @@ func main() {
|
|||||||
Usage: "execute commands",
|
Usage: "execute commands",
|
||||||
EnvVar: "PLUGIN_SCRIPT,SSH_SCRIPT",
|
EnvVar: "PLUGIN_SCRIPT,SSH_SCRIPT",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.BoolFlag{
|
||||||
Name: "env-file",
|
Name: "script.stop",
|
||||||
Usage: "source env file",
|
Usage: "stop script after first failure",
|
||||||
|
EnvVar: "PLUGIN_SCRIPT_STOP",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "proxy.ssh-key",
|
Name: "proxy.ssh-key",
|
||||||
@@ -115,6 +134,21 @@ func main() {
|
|||||||
Usage: "proxy connection timeout",
|
Usage: "proxy connection timeout",
|
||||||
EnvVar: "PLUGIN_PROXY_TIMEOUT,PROXY_SSH_TIMEOUT",
|
EnvVar: "PLUGIN_PROXY_TIMEOUT,PROXY_SSH_TIMEOUT",
|
||||||
},
|
},
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "secrets",
|
||||||
|
Usage: "plugin secret",
|
||||||
|
EnvVar: "PLUGIN_SECRETS",
|
||||||
|
},
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "envs",
|
||||||
|
Usage: "Pass envs",
|
||||||
|
EnvVar: "PLUGIN_ENVS",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "debug",
|
||||||
|
Usage: "debug mode",
|
||||||
|
EnvVar: "PLUGIN_DEBUG",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override a template
|
// Override a template
|
||||||
@@ -150,14 +184,13 @@ REPOSITORY:
|
|||||||
Github: https://github.com/appleboy/drone-ssh
|
Github: https://github.com/appleboy/drone-ssh
|
||||||
`
|
`
|
||||||
|
|
||||||
app.Run(os.Args)
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Println("drone-ssh error: ", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(c *cli.Context) error {
|
func run(c *cli.Context) error {
|
||||||
if c.String("env-file") != "" {
|
|
||||||
_ = godotenv.Load(c.String("env-file"))
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
Key: c.String("ssh-key"),
|
Key: c.String("ssh-key"),
|
||||||
@@ -169,6 +202,11 @@ func run(c *cli.Context) error {
|
|||||||
Timeout: c.Duration("timeout"),
|
Timeout: c.Duration("timeout"),
|
||||||
CommandTimeout: c.Int("command.timeout"),
|
CommandTimeout: c.Int("command.timeout"),
|
||||||
Script: c.StringSlice("script"),
|
Script: c.StringSlice("script"),
|
||||||
|
ScriptStop: c.Bool("script.stop"),
|
||||||
|
Secrets: c.StringSlice("secrets"),
|
||||||
|
Envs: c.StringSlice("envs"),
|
||||||
|
Debug: c.Bool("debug"),
|
||||||
|
Sync: c.Bool("sync"),
|
||||||
Proxy: easyssh.DefaultConfig{
|
Proxy: easyssh.DefaultConfig{
|
||||||
Key: c.String("proxy.ssh-key"),
|
Key: c.String("proxy.ssh-key"),
|
||||||
KeyPath: c.String("proxy.key-path"),
|
KeyPath: c.String("proxy.key-path"),
|
||||||
@@ -179,6 +217,7 @@ func run(c *cli.Context) error {
|
|||||||
Timeout: c.Duration("proxy.timeout"),
|
Timeout: c.Duration("proxy.timeout"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Writer: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugin.Exec()
|
return plugin.Exec()
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"io"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -11,12 +12,11 @@ import (
|
|||||||
"github.com/appleboy/easyssh-proxy"
|
"github.com/appleboy/easyssh-proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
missingHostOrUser = "Error: missing server host or user"
|
missingHostOrUser = "Error: missing server host or user"
|
||||||
missingPasswordOrKey = "Error: can't connect without a private SSH key or password"
|
missingPasswordOrKey = "Error: can't connect without a private SSH key or password"
|
||||||
commandTimeOut = "Error: command timeout"
|
commandTimeOut = "Error: command timeout"
|
||||||
|
setPasswordandKey = "can't set password and key at the same time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -31,71 +31,134 @@ type (
|
|||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
CommandTimeout int
|
CommandTimeout int
|
||||||
Script []string
|
Script []string
|
||||||
|
ScriptStop bool
|
||||||
|
Secrets []string
|
||||||
|
Envs []string
|
||||||
Proxy easyssh.DefaultConfig
|
Proxy easyssh.DefaultConfig
|
||||||
|
Debug bool
|
||||||
|
Sync bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugin structure
|
// Plugin structure
|
||||||
Plugin struct {
|
Plugin struct {
|
||||||
Config Config
|
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{
|
||||||
|
Server: host,
|
||||||
|
User: p.Config.UserName,
|
||||||
|
Password: p.Config.Password,
|
||||||
|
Port: strconv.Itoa(p.Config.Port),
|
||||||
|
Key: p.Config.Key,
|
||||||
|
KeyPath: p.Config.KeyPath,
|
||||||
|
Timeout: p.Config.Timeout,
|
||||||
|
Proxy: easyssh.DefaultConfig{
|
||||||
|
Server: p.Config.Proxy.Server,
|
||||||
|
User: p.Config.Proxy.User,
|
||||||
|
Password: p.Config.Proxy.Password,
|
||||||
|
Port: p.Config.Proxy.Port,
|
||||||
|
Key: p.Config.Proxy.Key,
|
||||||
|
KeyPath: p.Config.Proxy.KeyPath,
|
||||||
|
Timeout: p.Config.Proxy.Timeout,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p.log(host, "======CMD======")
|
||||||
|
p.log(host, strings.Join(p.Config.Script, "\n"))
|
||||||
|
p.log(host, "======END======")
|
||||||
|
|
||||||
|
env := []string{}
|
||||||
|
for _, key := range p.Config.Envs {
|
||||||
|
key = strings.ToUpper(key)
|
||||||
|
if val, found := os.LookupEnv(key); found {
|
||||||
|
env = append(env, key+"="+escapeArg(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Config.Script = append(env, p.scriptCommands()...)
|
||||||
|
|
||||||
|
if p.Config.Debug {
|
||||||
|
p.log(host, "======ENV======")
|
||||||
|
p.log(host, strings.Join(env, "\n"))
|
||||||
|
p.log(host, "======END======")
|
||||||
|
}
|
||||||
|
|
||||||
|
stdoutChan, stderrChan, doneChan, errChan, err := ssh.Stream(strings.Join(p.Config.Script, "\n"), p.Config.CommandTimeout)
|
||||||
|
if err != nil {
|
||||||
|
errChannel <- err
|
||||||
|
} else {
|
||||||
|
// read from the output channel until the done signal is passed
|
||||||
|
isTimeout := true
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case isTimeout = <-doneChan:
|
||||||
|
break loop
|
||||||
|
case outline := <-stdoutChan:
|
||||||
|
p.log(host, "out:", outline)
|
||||||
|
case errline := <-stderrChan:
|
||||||
|
p.log(host, "err:", errline)
|
||||||
|
case err = <-errChan:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get exit code or command error.
|
||||||
|
if err != nil {
|
||||||
|
errChannel <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
// command time out
|
||||||
|
if !isTimeout {
|
||||||
|
errChannel <- fmt.Errorf(commandTimeOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
func (p Plugin) log(host string, message ...interface{}) {
|
func (p Plugin) log(host string, message ...interface{}) {
|
||||||
log.Printf("%s: %s", host, fmt.Sprintln(message...))
|
if p.Writer == nil {
|
||||||
|
p.Writer = os.Stdout
|
||||||
|
}
|
||||||
|
if count := len(p.Config.Host); count == 1 {
|
||||||
|
fmt.Fprintf(p.Writer, "%s", fmt.Sprintln(message...))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(p.Writer, "%s: %s", host, fmt.Sprintln(message...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the plugin.
|
// Exec executes the plugin.
|
||||||
func (p Plugin) Exec() error {
|
func (p Plugin) Exec() error {
|
||||||
if len(p.Config.Host) == 0 && p.Config.UserName == "" {
|
if len(p.Config.Host) == 0 && len(p.Config.UserName) == 0 {
|
||||||
return fmt.Errorf(missingHostOrUser)
|
return fmt.Errorf(missingHostOrUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Config.Key == "" && p.Config.Password == "" && p.Config.KeyPath == "" {
|
if len(p.Config.Key) == 0 && len(p.Config.Password) == 0 && len(p.Config.KeyPath) == 0 {
|
||||||
return fmt.Errorf(missingPasswordOrKey)
|
return fmt.Errorf(missingPasswordOrKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(p.Config.Key) != 0 && len(p.Config.Password) != 0 {
|
||||||
|
return fmt.Errorf(setPasswordandKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(len(p.Config.Host))
|
wg.Add(len(p.Config.Host))
|
||||||
errChannel := make(chan error, 1)
|
errChannel := make(chan error, 1)
|
||||||
finished := make(chan bool, 1)
|
finished := make(chan bool, 1)
|
||||||
for _, host := range p.Config.Host {
|
for _, host := range p.Config.Host {
|
||||||
go func(host string) {
|
if p.Config.Sync {
|
||||||
// Create MakeConfig instance with remote username, server address and path to private key.
|
p.exec(host, &wg, errChannel)
|
||||||
ssh := &easyssh.MakeConfig{
|
} else {
|
||||||
Server: host,
|
go p.exec(host, &wg, errChannel)
|
||||||
User: p.Config.UserName,
|
}
|
||||||
Password: p.Config.Password,
|
|
||||||
Port: strconv.Itoa(p.Config.Port),
|
|
||||||
Key: p.Config.Key,
|
|
||||||
KeyPath: p.Config.KeyPath,
|
|
||||||
Timeout: p.Config.Timeout,
|
|
||||||
Proxy: easyssh.DefaultConfig{
|
|
||||||
Server: p.Config.Proxy.Server,
|
|
||||||
User: p.Config.Proxy.User,
|
|
||||||
Password: p.Config.Proxy.Password,
|
|
||||||
Port: p.Config.Proxy.Port,
|
|
||||||
Key: p.Config.Proxy.Key,
|
|
||||||
KeyPath: p.Config.Proxy.KeyPath,
|
|
||||||
Timeout: p.Config.Proxy.Timeout,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
p.log(host, "commands: ", strings.Join(p.Config.Script, "\n"))
|
|
||||||
outStr, errStr, isTimeout, err := ssh.Run(strings.Join(p.Config.Script, "\n"), p.Config.CommandTimeout)
|
|
||||||
p.log(host, "outputs:", outStr)
|
|
||||||
if len(errStr) != 0 {
|
|
||||||
p.log(host, "errors:", errStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errChannel <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isTimeout {
|
|
||||||
errChannel <- fmt.Errorf(commandTimeOut)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Done()
|
|
||||||
}(host)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -107,12 +170,32 @@ func (p Plugin) Exec() error {
|
|||||||
case <-finished:
|
case <-finished:
|
||||||
case err := <-errChannel:
|
case err := <-errChannel:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("drone-ssh error: ", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Successfully executed commands to all host.")
|
fmt.Println("==========================================")
|
||||||
|
fmt.Println("Successfully executed commands to all host.")
|
||||||
|
fmt.Println("==========================================")
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
+386
@@ -1,6 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/appleboy/easyssh-proxy"
|
"github.com/appleboy/easyssh-proxy"
|
||||||
@@ -22,6 +25,7 @@ func TestMissingKeyOrPassword(t *testing.T) {
|
|||||||
Host: []string{"localhost"},
|
Host: []string{"localhost"},
|
||||||
UserName: "ubuntu",
|
UserName: "ubuntu",
|
||||||
},
|
},
|
||||||
|
os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := plugin.Exec()
|
err := plugin.Exec()
|
||||||
@@ -30,6 +34,23 @@ func TestMissingKeyOrPassword(t *testing.T) {
|
|||||||
assert.Equal(t, missingPasswordOrKey, err.Error())
|
assert.Equal(t, missingPasswordOrKey, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetPasswordAndKey(t *testing.T) {
|
||||||
|
plugin := Plugin{
|
||||||
|
Config{
|
||||||
|
Host: []string{"localhost"},
|
||||||
|
UserName: "ubuntu",
|
||||||
|
Password: "1234",
|
||||||
|
Key: "1234",
|
||||||
|
},
|
||||||
|
os.Stdout,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Exec()
|
||||||
|
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, setPasswordandKey, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
func TestIncorrectPassword(t *testing.T) {
|
func TestIncorrectPassword(t *testing.T) {
|
||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
@@ -105,6 +126,39 @@ func TestSSHScriptFromKeyFile(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStreamFromSSHCommand(t *testing.T) {
|
||||||
|
plugin := Plugin{
|
||||||
|
Config: Config{
|
||||||
|
Host: []string{"localhost", "127.0.0.1"},
|
||||||
|
UserName: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Script: []string{"whoami", "for i in {1..5}; do echo ${i}; sleep 1; done", "echo 'done'"},
|
||||||
|
CommandTimeout: 60,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Exec()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHScriptWithError(t *testing.T) {
|
||||||
|
plugin := Plugin{
|
||||||
|
Config: Config{
|
||||||
|
Host: []string{"localhost", "127.0.0.1"},
|
||||||
|
UserName: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Script: []string{"exit 1"},
|
||||||
|
CommandTimeout: 60,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Exec()
|
||||||
|
// Process exited with status 1
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSSHCommandTimeOut(t *testing.T) {
|
func TestSSHCommandTimeOut(t *testing.T) {
|
||||||
plugin := Plugin{
|
plugin := Plugin{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
@@ -142,3 +196,335 @@ func TestProxyCommand(t *testing.T) {
|
|||||||
err := plugin.Exec()
|
err := plugin.Exec()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSSHCommandError(t *testing.T) {
|
||||||
|
plugin := Plugin{
|
||||||
|
Config: Config{
|
||||||
|
Host: []string{"localhost"},
|
||||||
|
UserName: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Script: []string{"mkdir a", "mkdir a"},
|
||||||
|
CommandTimeout: 60,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Exec()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSHCommandExitCodeError(t *testing.T) {
|
||||||
|
plugin := Plugin{
|
||||||
|
Config: Config{
|
||||||
|
Host: []string{"localhost"},
|
||||||
|
UserName: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Script: []string{
|
||||||
|
"set -e",
|
||||||
|
"echo 1",
|
||||||
|
"mkdir a",
|
||||||
|
"mkdir a",
|
||||||
|
"echo 2",
|
||||||
|
},
|
||||||
|
CommandTimeout: 60,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := plugin.Exec()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetENV(t *testing.T) {
|
||||||
|
os.Setenv("FOO", `' 1) '`)
|
||||||
|
plugin := Plugin{
|
||||||
|
Config: Config{
|
||||||
|
Host: []string{"localhost"},
|
||||||
|
UserName: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Secrets: []string{"FOO"},
|
||||||
|
Envs: []string{"foo"},
|
||||||
|
Debug: true,
|
||||||
|
Script: []string{"whoami; echo $FOO"},
|
||||||
|
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 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{
|
||||||
|
Host: []string{"localhost", "127.0.0.1"},
|
||||||
|
UserName: "drone-scp",
|
||||||
|
Port: 22,
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Script: []string{"whoami", "for i in {1..3}; do echo ${i}; sleep 1; done", "echo 'done'"},
|
||||||
|
CommandTimeout: 60,
|
||||||
|
Sync: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
+6
-6
@@ -1,8 +1,7 @@
|
|||||||
.PHONY: test drone-ssh fmt vet errcheck lint install update coverage embedmd
|
.PHONY: test drone-ssh fmt vet errcheck lint install update coverage embedmd
|
||||||
|
|
||||||
GOFMT ?= gofmt "-s"
|
GOFMT ?= gofmt "-s"
|
||||||
|
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
|
||||||
GOFILES := find . -name "*.go" -type f -not -path "./vendor/*"
|
|
||||||
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
|
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
|
||||||
|
|
||||||
all: install lint
|
all: install lint
|
||||||
@@ -14,16 +13,17 @@ install:
|
|||||||
govendor sync
|
govendor sync
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
$(GOFILES) | xargs $(GOFMT) -w
|
$(GOFMT) -w $(GOFILES)
|
||||||
|
|
||||||
.PHONY: fmt-check
|
.PHONY: fmt-check
|
||||||
fmt-check:
|
fmt-check:
|
||||||
# get all go files and run go fmt on them
|
# get all go files and run go fmt on them
|
||||||
@files=$$($(GOFILES) | xargs $(GOFMT) -l); if [ -n "$$files" ]; then \
|
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||||
|
if [ -n "$$diff" ]; then \
|
||||||
echo "Please run 'make fmt' and commit the result:"; \
|
echo "Please run 'make fmt' and commit the result:"; \
|
||||||
echo "$${files}"; \
|
echo "$${diff}"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet $(PACKAGES)
|
go vet $(PACKAGES)
|
||||||
|
|||||||
+56
-1
@@ -1,6 +1,11 @@
|
|||||||
# easyssh-proxy
|
# easyssh-proxy
|
||||||
|
|
||||||
[](https://godoc.org/github.com/appleboy/easyssh-proxy) [](http://drone.wu-boy.com/appleboy/easyssh-proxy) [](https://codecov.io/gh/appleboy/easyssh-proxy) [](https://goreportcard.com/report/github.com/appleboy/easyssh-proxy) [](https://sourcegraph.com/github.com/appleboy/easyssh-proxy?badge)
|
[](https://godoc.org/github.com/appleboy/easyssh-proxy)
|
||||||
|
[](http://drone.wu-boy.com/appleboy/easyssh-proxy)
|
||||||
|
[](https://codecov.io/gh/appleboy/easyssh-proxy)
|
||||||
|
[](https://goreportcard.com/report/github.com/appleboy/easyssh-proxy)
|
||||||
|
[](https://sourcegraph.com/github.com/appleboy/easyssh-proxy?badge)
|
||||||
|
[](https://github.com/appleboy/easyssh-proxy/releases/latest)
|
||||||
|
|
||||||
easyssh-proxy provides a simple implementation of some SSH protocol features in Go.
|
easyssh-proxy provides a simple implementation of some SSH protocol features in Go.
|
||||||
|
|
||||||
@@ -124,3 +129,53 @@ See [example/proxy/proxy.go](./example/proxy/proxy.go)
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### SSH Stream Log
|
||||||
|
|
||||||
|
See [example/stream/stream.go](./example/stream/stream.go)
|
||||||
|
|
||||||
|
[embedmd]:# (example/stream/stream.go go /func/ /^}$/)
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
// Create MakeConfig instance with remote username, server address and path to private key.
|
||||||
|
ssh := &easyssh.MakeConfig{
|
||||||
|
Server: "localhost",
|
||||||
|
User: "drone-scp",
|
||||||
|
KeyPath: "./tests/.ssh/id_rsa",
|
||||||
|
Port: "22",
|
||||||
|
Timeout: 60 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Run method with command you want to run on remote server.
|
||||||
|
stdoutChan, stderrChan, doneChan, errChan, err := ssh.Stream("for i in {1..5}; do echo ${i}; sleep 1; done; exit 2;", 60)
|
||||||
|
// Handle errors
|
||||||
|
if err != nil {
|
||||||
|
panic("Can't run remote command: " + err.Error())
|
||||||
|
} else {
|
||||||
|
// read from the output channel until the done signal is passed
|
||||||
|
isTimeout := true
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case isTimeout = <-doneChan:
|
||||||
|
break loop
|
||||||
|
case outline := <-stdoutChan:
|
||||||
|
fmt.Println("out:", outline)
|
||||||
|
case errline := <-stderrChan:
|
||||||
|
fmt.Println("err:", errline)
|
||||||
|
case err = <-errChan:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get exit code or command error.
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("err: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// command time out
|
||||||
|
if !isTimeout {
|
||||||
|
fmt.Println("Error: command timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
+53
-41
@@ -86,8 +86,9 @@ func getSSHConfig(config DefaultConfig) *ssh.ClientConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Key != "" {
|
if config.Key != "" {
|
||||||
signer, _ := ssh.ParsePrivateKey([]byte(config.Key))
|
if signer, err := ssh.ParsePrivateKey([]byte(config.Key)); err == nil {
|
||||||
auths = append(auths, ssh.PublicKeys(signer))
|
auths = append(auths, ssh.PublicKeys(signer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ssh.ClientConfig{
|
return &ssh.ClientConfig{
|
||||||
@@ -155,36 +156,45 @@ func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) {
|
|||||||
// Stream returns one channel that combines the stdout and stderr of the command
|
// Stream returns one channel that combines the stdout and stderr of the command
|
||||||
// as it is run on the remote machine, and another that sends true when the
|
// as it is run on the remote machine, and another that sends true when the
|
||||||
// command is done. The sessions and channels will then be closed.
|
// command is done. The sessions and channels will then be closed.
|
||||||
func (ssh_conf *MakeConfig) Stream(command string, timeout int) (stdout chan string, stderr chan string, done chan bool, err error) {
|
func (ssh_conf *MakeConfig) Stream(command string, timeout int) (<-chan string, <-chan string, <-chan bool, <-chan error, error) {
|
||||||
// connect to remote host
|
|
||||||
session, err := ssh_conf.connect()
|
|
||||||
if err != nil {
|
|
||||||
return stdout, stderr, done, err
|
|
||||||
}
|
|
||||||
// connect to both outputs (they are of type io.Reader)
|
|
||||||
outReader, err := session.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return stdout, stderr, done, err
|
|
||||||
}
|
|
||||||
errReader, err := session.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return stdout, stderr, done, err
|
|
||||||
}
|
|
||||||
// combine outputs, create a line-by-line scanner
|
|
||||||
stdoutReader := io.MultiReader(outReader)
|
|
||||||
stderrReader := io.MultiReader(errReader)
|
|
||||||
err = session.Start(command)
|
|
||||||
stdoutScanner := bufio.NewScanner(stdoutReader)
|
|
||||||
stderrScanner := bufio.NewScanner(stderrReader)
|
|
||||||
// continuously send the command's output over the channel
|
// continuously send the command's output over the channel
|
||||||
stdoutChan := make(chan string)
|
stdoutChan := make(chan string)
|
||||||
stderrChan := make(chan string)
|
stderrChan := make(chan string)
|
||||||
done = make(chan bool)
|
doneChan := make(chan bool)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
go func(stdoutScanner, stderrScanner *bufio.Scanner, stdoutChan, stderrChan chan string, done chan bool) {
|
// connect to remote host
|
||||||
|
session, err := ssh_conf.connect()
|
||||||
|
if err != nil {
|
||||||
|
return stdoutChan, stderrChan, doneChan, errChan, err
|
||||||
|
}
|
||||||
|
// defer session.Close()
|
||||||
|
// connect to both outputs (they are of type io.Reader)
|
||||||
|
outReader, err := session.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return stdoutChan, stderrChan, doneChan, errChan, err
|
||||||
|
}
|
||||||
|
errReader, err := session.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return stdoutChan, stderrChan, doneChan, errChan, err
|
||||||
|
}
|
||||||
|
err = session.Start(command)
|
||||||
|
if err != nil {
|
||||||
|
return stdoutChan, stderrChan, doneChan, errChan, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// combine outputs, create a line-by-line scanner
|
||||||
|
stdoutReader := io.MultiReader(outReader)
|
||||||
|
stderrReader := io.MultiReader(errReader)
|
||||||
|
stdoutScanner := bufio.NewScanner(stdoutReader)
|
||||||
|
stderrScanner := bufio.NewScanner(stderrReader)
|
||||||
|
|
||||||
|
go func(stdoutScanner, stderrScanner *bufio.Scanner, stdoutChan, stderrChan chan string, doneChan chan bool, errChan chan error) {
|
||||||
defer close(stdoutChan)
|
defer close(stdoutChan)
|
||||||
defer close(stderrChan)
|
defer close(stderrChan)
|
||||||
defer close(done)
|
defer close(doneChan)
|
||||||
|
defer close(errChan)
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
timeoutChan := time.After(time.Duration(timeout) * time.Second)
|
timeoutChan := time.After(time.Duration(timeout) * time.Second)
|
||||||
res := make(chan bool, 1)
|
res := make(chan bool, 1)
|
||||||
@@ -202,32 +212,30 @@ func (ssh_conf *MakeConfig) Stream(command string, timeout int) (stdout chan str
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case <-res:
|
case <-res:
|
||||||
stdoutChan <- ""
|
errChan <- session.Wait()
|
||||||
stderrChan <- ""
|
doneChan <- true
|
||||||
done <- true
|
|
||||||
case <-timeoutChan:
|
case <-timeoutChan:
|
||||||
stdoutChan <- ""
|
|
||||||
stderrChan <- "Run Command Timeout!"
|
stderrChan <- "Run Command Timeout!"
|
||||||
done <- false
|
errChan <- nil
|
||||||
|
doneChan <- false
|
||||||
}
|
}
|
||||||
|
}(stdoutScanner, stderrScanner, stdoutChan, stderrChan, doneChan, errChan)
|
||||||
|
|
||||||
session.Close()
|
return stdoutChan, stderrChan, doneChan, errChan, err
|
||||||
}(stdoutScanner, stderrScanner, stdoutChan, stderrChan, done)
|
|
||||||
return stdoutChan, stderrChan, done, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run command on remote machine and returns its stdout as a string
|
// Run command on remote machine and returns its stdout as a string
|
||||||
func (ssh_conf *MakeConfig) Run(command string, timeout int) (outStr string, errStr string, isTimeout bool, err error) {
|
func (ssh_conf *MakeConfig) Run(command string, timeout int) (outStr string, errStr string, isTimeout bool, err error) {
|
||||||
stdoutChan, stderrChan, doneChan, err := ssh_conf.Stream(command, timeout)
|
stdoutChan, stderrChan, doneChan, errChan, err := ssh_conf.Stream(command, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return outStr, errStr, isTimeout, err
|
return outStr, errStr, isTimeout, err
|
||||||
}
|
}
|
||||||
// read from the output channel until the done signal is passed
|
// read from the output channel until the done signal is passed
|
||||||
stillGoing := true
|
loop:
|
||||||
for stillGoing {
|
for {
|
||||||
select {
|
select {
|
||||||
case isTimeout = <-doneChan:
|
case isTimeout = <-doneChan:
|
||||||
stillGoing = false
|
break loop
|
||||||
case outline := <-stdoutChan:
|
case outline := <-stdoutChan:
|
||||||
if outline != "" {
|
if outline != "" {
|
||||||
outStr += outline + "\n"
|
outStr += outline + "\n"
|
||||||
@@ -236,6 +244,7 @@ func (ssh_conf *MakeConfig) Run(command string, timeout int) (outStr string, err
|
|||||||
if errline != "" {
|
if errline != "" {
|
||||||
errStr += errline + "\n"
|
errStr += errline + "\n"
|
||||||
}
|
}
|
||||||
|
case err = <-errChan:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// return the concatenation of all signals from the output channel
|
// return the concatenation of all signals from the output channel
|
||||||
@@ -266,17 +275,20 @@ func (ssh_conf *MakeConfig) Scp(sourceFile string, etargetFile string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
w, _ := session.StdinPipe()
|
w, err := session.StdinPipe()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
fmt.Fprintln(w, "C0644", srcStat.Size(), targetFile)
|
fmt.Fprintln(w, "C0644", srcStat.Size(), targetFile)
|
||||||
|
|
||||||
if srcStat.Size() > 0 {
|
if srcStat.Size() > 0 {
|
||||||
io.Copy(w, src)
|
io.Copy(w, src)
|
||||||
fmt.Fprint(w, "\x00")
|
fmt.Fprint(w, "\x00")
|
||||||
w.Close()
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprint(w, "\x00")
|
fmt.Fprint(w, "\x00")
|
||||||
w.Close()
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
Vendored
+5
-5
@@ -3,12 +3,12 @@
|
|||||||
"ignore": "test",
|
"ignore": "test",
|
||||||
"package": [
|
"package": [
|
||||||
{
|
{
|
||||||
"checksumSHA1": "L3PugNJJOEpRmRbD+27LgTZC2E4=",
|
"checksumSHA1": "EcF7T9tPEMMJfuRdPBB3NdRUg4c=",
|
||||||
"path": "github.com/appleboy/easyssh-proxy",
|
"path": "github.com/appleboy/easyssh-proxy",
|
||||||
"revision": "a13ed86767b8e8a24d8147a4909a702e7cf6b465",
|
"revision": "33d87eae3a018c3312e32cc4eb4578d5a563aabd",
|
||||||
"revisionTime": "2017-04-14T13:46:38Z",
|
"revisionTime": "2017-05-16T07:22:25Z",
|
||||||
"version": "1.1.2",
|
"version": "1.1.6",
|
||||||
"versionExact": "1.1.2"
|
"versionExact": "1.1.6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=",
|
"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=",
|
||||||
|
|||||||
Reference in New Issue
Block a user