From 355d82c611d9c02478c227834fd7fab18008efcf Mon Sep 17 00:00:00 2001 From: greg Date: Fri, 14 Jul 2023 09:37:35 +0200 Subject: [PATCH] push --- DOCS.md | 86 ++++++++++++++++++++++++++++++ Dockerfile | 6 +++ LICENSE | 21 ++++++++ README.md | 30 +++++++++++ upload.sh | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 296 insertions(+) create mode 100644 DOCS.md create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100755 upload.sh diff --git a/DOCS.md b/DOCS.md new file mode 100644 index 0000000..6e9839b --- /dev/null +++ b/DOCS.md @@ -0,0 +1,86 @@ +Use the Rsync plugin to synchronize files to remote hosts, and execute arbitrary commands on those hosts. + +## Config +The following parameters are used to configure the plugin: +- **user** - user to log in as on the remote machines, defaults to `root` +- **key** - private SSH key for the remote machines +- **hosts** - hostnames or ip-addresses of the remote machines +- **port** - port to connect to on the remote machines, defaults to `22` +- **source** - source folder to synchronize from, defaults to `./` +- **target** - target folder on remote machines to synchronize to +- **include** - rsync include filter +- **exclude** - rsync exclude filter +- **recursive** - recursively synchronize, defaults to `false` +- **delete** - delete target folder contents, defaults to `false` +- **args** - instruct plugin to use these additional rsync CLI arguments, example: `"--blocking-io"` +- **prescript** - list of commands to execute on remote machines before rsync occurs +- **script** - list of commands to execute on remote machines after rsync occurs +- **log_level** - ssh log level, defaults to quiet + +It is highly recommended to put your private key into a secret (`rsync_key`) so it is not exposed to users. This can be done using the drone-cli: + +```sh +drone secret add \ + --repository your/repo \ + --name rsync_key \ + --data @./id_rsa \ +``` + +Add the secret to your `.drone.yml`: +```yaml +kind: pipeline + +steps: +- name: rsync + image: drillster/drone-rsync + settings: + user: some-user + key: + from_secret: rsync_key + hosts: + - remote1 + source: ./dist + target: ~/packages + secrets: [ rsync_key ] +``` + +See the [secret guides](https://docs.drone.io/user-guide/secrets/pre-repository/) for additional information on secrets. + +## Examples +```yaml +kind: pipeline +name: default + +steps: +- name: rsync + image: drillster/drone-rsync + settings: + hosts: + - remote1 + - remote2 + user: + from_secret: rsync_user + key: + from_secret: rsync_key + source: ./dist + target: ~/packages + include: + - "app.tar.gz" + - "app.tar.gz.md5" + exclude: + - "**.*" + prescript: + - cd ~/packages + - md5sum -c app.tar.gz.md5 + - tar -xf app.tar.gz -C ~/app + script: + - cd ~/packages + - md5sum -c app.tar.gz.md5 + - tar -xf app.tar.gz -C ~/app +``` + +The example above illustrates a situation where an app package (`app.tar.gz`) will be deployed to 2 remote hosts (`remote1` and `remote2`). An md5 checksum will be deployed as well. After deploying, the md5 checksum is used to check the deployed package. If successful the package is extracted. + +## Important +The script passed to **script** will be executed on remote machines directly after rsync completes to deploy the files. It will be executed step by step until a command returns a non-zero exit-code. If this happens, the entire plugin will exit and fail the build. + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e2d2771 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM alpine:latest + +RUN apk add --no-cache --update ca-certificates bash openssh-client rsync +COPY upload.sh /usr/local/ + +ENTRYPOINT ["/usr/local/upload.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5671070 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Drillster + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..395e19e --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# drone-rsync +[![drone-rsync on Docker Hub](https://img.shields.io/docker/automated/drillster/drone-rsync.svg)](https://hub.docker.com/r/drillster/drone-rsync/) + +This is a pure Bash [Drone](https://github.com/drone/drone) >= 0.5 plugin to sync files to remote hosts. + +For more information on how to use the plugin, please take a look at [the docs](https://github.com/Drillster/drone-rsync/blob/master/DOCS.md). + +## Docker +Build the docker image by running: + +```bash +docker build --rm=true -t drillster/drone-rsync . +``` + +## Usage +Execute from the working directory (assuming you have an SSH server running on 127.0.0.1:22): + +```bash +docker run --rm \ + -e PLUGIN_KEY=$(cat some-private-key) \ + -e PLUGIN_HOSTS="127.0.0.1, 127.0.0.2, 127.0.0.3" \ + -e PLUGIN_PORTS="22, 23, 24" \ + -e PLUGIN_TARGET="./" \ + -e PLUGIN_PRESCRIPT="echo \"Prescript Done!\"" \ + -e PLUGIN_SCRIPT="echo \"Postscript Done!\"" \ + -e PLUGIN_ARGS="--blocking-io" \ + -v $(pwd):$(pwd) \ + -w $(pwd) \ + drillster/drone-rsync +``` diff --git a/upload.sh b/upload.sh new file mode 100755 index 0000000..404b327 --- /dev/null +++ b/upload.sh @@ -0,0 +1,153 @@ +#!/bin/bash +if [ -z "$PLUGIN_HOSTS" ]; then + echo "Specify at least one host!" + exit 1 +fi + +if [ -z "$PLUGIN_TARGET" ]; then + echo "Specify a target!" + exit 1 +fi + +DEFAULT_PORT=$PLUGIN_PORT +if [ -z "$PLUGIN_PORT" ]; then + echo "Port not specified, using default port 22!" + DEFAULT_PORT="22" +fi + +SOURCE=$PLUGIN_SOURCE +if [ -z "$PLUGIN_SOURCE" ]; then + echo "No source folder specified, using default './'" + SOURCE="./" +fi + +USER=$RSYNC_USER +if [ -z "$RSYNC_USER" ]; then + if [ -z "$PLUGIN_USER" ]; then + echo "No user specified, using root!" + USER="root" + else + USER=$PLUGIN_USER + fi +fi + +SSH_KEY=$RSYNC_KEY +if [ -z "$RSYNC_KEY" ]; then + if [ -z "$PLUGIN_KEY" ]; then + echo "No private key specified!" + exit 1 + fi + SSH_KEY=$PLUGIN_KEY +fi + +if [ -z "$PLUGIN_ARGS" ]; then + ARGS= +else + ARGS=$PLUGIN_ARGS +fi + +if [ -z "$PLUGIN_LOG_LEVEL" ]; then + LOG_LEVEL=quiet +else + LOG_LEVEL=$PLUGIN_LOG_LEVEL +fi + +# Building rsync command +expr="rsync -az $ARGS" + +if [[ -n "$PLUGIN_RECURSIVE" && "$PLUGIN_RECURSIVE" == "true" ]]; then + expr="$expr -r" +fi + +if [[ -n "$PLUGIN_DELETE" && "$PLUGIN_DELETE" == "true" ]]; then + expr="$expr --del" +fi + +expr="$expr -e 'ssh -p %s -o UserKnownHostsFile=/dev/null -o LogLevel=$LOG_LEVEL -o StrictHostKeyChecking=no'" + +# Include +IFS=','; read -ra INCLUDE <<< "$PLUGIN_INCLUDE" +for include in "${INCLUDE[@]}"; do + expr="$expr --include=$include" +done + +# Exclude +IFS=','; read -ra EXCLUDE <<< "$PLUGIN_EXCLUDE" +for exclude in "${EXCLUDE[@]}"; do + expr="$expr --exclude=$exclude" +done + +# Filter +IFS=','; read -ra FILTER <<< "$PLUGIN_FILTER" +for filter in "${FILTER[@]}"; do + expr="$expr --filter=$filter" +done + +expr="$expr $SOURCE" + +# Prepare SSH +home="/root" + +mkdir -p "$home/.ssh" + +printf "StrictHostKeyChecking no\n" > "$home/.ssh/config" +chmod 0700 "$home/.ssh/config" + +keyfile="$home/.ssh/id_rsa" +echo "$SSH_KEY" | grep -q "ssh-ed25519" +if [ $? -eq 0 ]; then + printf "Using ed25519 based key\n" + keyfile="$home/.ssh/id_ed25519" +fi +echo "$SSH_KEY" | grep -q "ecdsa-" +if [ $? -eq 0 ]; then + printf "Using ecdsa based key\n" + keyfile="$home/.ssh/id_ecdsa" +fi +echo "$SSH_KEY" > $keyfile +chmod 0600 $keyfile + +function join_with { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; } + +# Parse SSH precommands +IFS=','; read -ra COMMANDS <<< "$PLUGIN_PRESCRIPT" +prescript=$(join_with ' && ' "${COMMANDS[@]}") +# Parse SSH postcommands +IFS=','; read -ra COMMANDS <<< "$PLUGIN_SCRIPT" +postscript=$(join_with ' && ' "${COMMANDS[@]}") + +# Run rsync +IFS=','; read -ra HOSTS <<< "$PLUGIN_HOSTS" +IFS=','; read -ra PORTS <<< "$PLUGIN_PORTS" +result=0 +for ((i=0; i < ${#HOSTS[@]}; i++)) +do + HOST=${HOSTS[$i]} + PORT=${PORTS[$i]} + if [ -z $PORT ] + then + # Default Port 22 + PORT=$DEFAULT_PORT + fi + echo $(printf "%s" "$ $(printf "$expr" "$PORT") $USER@$HOST:$PLUGIN_TARGET ...") + if [ -n "$PLUGIN_PRESCRIPT" ]; then + echo $(printf "%s" "$ ssh -p $PORT $USER@$HOST ...") + echo $(printf "%s" " > $prescript ...") + eval "ssh -p $PORT $USER@$HOST '$prescript'" + result=$(($result+$?)) + echo $(printf "%s" "$ ssh -p $PORT $USER@$HOST result: $?") + if [ "$result" -gt "0" ]; then exit $result; fi + fi + eval "$(printf "$expr" "$PORT") $USER@$HOST:$PLUGIN_TARGET" + result=$(($result+$?)) + if [ "$result" -gt "0" ]; then exit $result; fi + if [ -n "$PLUGIN_SCRIPT" ]; then + echo $(printf "%s" "$ ssh -p $PORT $USER@$HOST ...") + echo $(printf "%s" " > $postscript ...") + eval "ssh -p $PORT $USER@$HOST '$postscript'" + result=$(($result+$?)) + echo $(printf "%s" "$ ssh -p $PORT $USER@$HOST result: $?") + if [ "$result" -gt "0" ]; then exit $result; fi + fi +done +exit $result