Skip to content
Permalink
Browse files

Merge pull request henry40408#49 from henry40408/feature/integrate-pl…

…aceholders

feat: Integrate placeholders
  • Loading branch information...
henry40408 committed Dec 20, 2017
2 parents 860d1d0 + b7d718b commit 69a3d938df5e097a34f8f3ba3d2f8fc281ffc0a6
1 .env

This file was deleted.

@@ -1,23 +1,17 @@
# build stage

FROM golang:alpine as builder

COPY . /go/src/github.com/henry40408/concourse-ssh-resource
ENV CGO_ENABLED 0

RUN apk --no-cache add make && \
cd /go/src/github.com/henry40408/concourse-ssh-resource && \
make build-linux && \
rm -r /go && \
apk --no-cache del make
cd /go/src/github.com/henry40408/concourse-ssh-resource && \
make build-linux

WORKDIR /opt/resource

################ thats our production image
# release stage

FROM alpine:edge

FROM alpine:edge AS resource
RUN apk --no-cache add \
bash \
curl \
gzip \
jq \
tar \
openssl
COPY --from=builder /opt/resource /opt/resource

This file was deleted.

@@ -12,11 +12,11 @@
## Source Configuration

- `host`: host name of remote machine
- `port`: port of SSH server on remote machine, `22` by default
- `user`: user for executing shell script on remote machine
- `password`: plain password for user on remote machine
- `private_key`: private SSH key for user on remote machine
* `host`: host name of remote machine
* `port`: port of SSH server on remote machine, `22` by default
* `user`: user for executing shell script on remote machine
* `password`: plain password for user on remote machine
* `private_key`: private SSH key for user on remote machine

### Caveats

@@ -32,24 +32,23 @@ Execute shell script on remote machine via SSH.

#### Parameters

- `interpreter`: string, path to interpreter on remote machine, e.g. `/usr/bin/python3`, `/bin/sh` by default
- `script`: string, shell script to run on remote machine
- `placeholders`: Hashmap of `name` and either `value ` for a static value, or `file` for a dynamic value read from a file.
Every string matches `name` in your script defintion will then be replaced by either the `value` or the content of `file`
* `interpreter`: string, path to interpreter on remote machine, e.g. `/usr/bin/python3`, `/bin/sh` by default
* `script`: string, shell script to run on remote machine
* `placeholders`: Map of `name` and either `value` for a static value, or `file` for a dynamic value read from a file. Every string matches `name` in your script defintion will then be replaced by either the `value` or the content of `file`. If `file` is used, **only the first line of file content would be used**. Example:

Example
```
- put: myserver
params:
interpreter: /usr/bin/env python3
script: |
echo "<MyPlaceHolder>"
echo "|dynamicPlaceHolder|"
placeholders:
- name: "<MyPlaceHolder>"
value: 'somevalue'
- name: "|dynamicPlaceHolder|"
file: "myresource/somefile"
```yaml
---
- put: myserver
params:
interpreter: /bin/sh
script: |
echo "<MyPlaceHolder>"
echo "|dynamicPlaceHolder|"
placeholders:
- name: "<MyPlaceHolder>"
value: "somevalue"
- name: "|dynamicPlaceHolder|"
file: "myresource/somefile"
```

## Examples
@@ -73,24 +72,40 @@ resources:
jobs:
- name: echo
plan:
# Basic usage
- put: staging-server
params:
interpreter: /usr/bin/env python3
script: |
print("Hello, world!")
# Placeholder usage
- put: staging-server
params:
interpreter: /bin/sh
script: |
echo "<static_value>"
echo "|dynamic_value|"
placeholders:
- name: "<static_value>"
value: "foo"
- name: "|dynamic_value|"
file: "bar"
```

## Build
## How to Test

We need to start a simple SSH server first. I assume there is no SSH server currently running on your laptop or workstation.

Build yourself
For more information about the SSH server, please checkout [henry40408/alpine-ssh](https://github.com/henry40408/alpine-ssh).

docker-compose build . -t you/concourse-ssh-resource
1. `docker run -d -p 22:22 quay.io/henry40408/alpine-ssh`
2. `make test`

## Tests
## Contributors

To run the test just do
> sorted in alphabetical order
docker-compose up
* [@EugenMayer](https://github.com/EugenMayer)

## License

@@ -6,7 +6,6 @@ import (
"testing"
"time"

"github.com/reconquest/hierr-go"
"github.com/stretchr/testify/assert"

"github.com/henry40408/concourse-ssh-resource/internal/models"
@@ -63,6 +62,5 @@ func TestCheckCommandWithMalformedJSON(t *testing.T) {
out := bytes.NewBuffer([]byte{})

err := checkCommand(in, out)
herr := err.(hierr.Error)
assert.Equal(t, "unable to parse JSON from standard input", herr.GetMessage())
assert.Contains(t, err.Error(), "unable to parse JSON from standard input")
}
@@ -2,10 +2,12 @@ package main

import (
"encoding/json"
"fmt"
"io"
"time"

hierr "github.com/reconquest/hierr-go"
"github.com/reconquest/hierr-go"
"github.com/spf13/afero"

"github.com/henry40408/concourse-ssh-resource/internal/models"
"github.com/henry40408/concourse-ssh-resource/internal/ssh"
@@ -21,17 +23,23 @@ type outResponse struct {
Metadata []models.Metadata `json:"metadata"`
}

func outCommand(stdin io.Reader, stdout, stderr io.Writer, baseDir string) error {
func outCommand(fs afero.Fs, args []string, stdin io.Reader, stdout, stderr io.Writer) error {
var request outRequest

if len(args) < 2 {
return fmt.Errorf("need base directory, usage: %s <base directory>", args[0])
}

baseDir := args[1]

err := json.NewDecoder(stdin).Decode(&request)
if err != nil {
return hierr.Errorf(err, "unable to parse JSON from standard input")
}

outWriter := &prefixWriter{prefix: "STDOUT", writer: stderr}
errWriter := &prefixWriter{prefix: "STDERR", writer: stderr}
err = ssh.PerformSSHCommand(&request.Source, &request.Params, outWriter, errWriter, baseDir)
err = ssh.PerformSSHCommand(fs, &request.Source, &request.Params, outWriter, errWriter, baseDir)
if err != nil {
return hierr.Errorf(err, "unable to run SSH command")
}
@@ -0,0 +1,84 @@
package main

import (
"bytes"
"io/ioutil"
"testing"

"github.com/spf13/afero"

"github.com/stretchr/testify/assert"
)

func TestOutCommandWithValuePlaceholder(t *testing.T) {
args := []string{"out", "/tmp"}
in := bytes.NewBufferString(`{
"source": {
"host": "localhost",
"user": "root",
"password": "toor"
},
"params": {
"interpreter": "/bin/sh",
"script": "echo <CODE_VERSION>",
"placeholders": [{
"name": "<CODE_VERSION>",
"value": "test_ok"
}]
},
"version": { "ref": "0.5.0" }
}`)
out := bytes.NewBuffer([]byte{})
stdErr := bytes.NewBuffer([]byte{})

fs := afero.NewMemMapFs()
err := outCommand(fs, args, in, out, stdErr)
if !assert.NoError(t, err) {
return
}

stdErrContent, err := ioutil.ReadAll(stdErr)
if !assert.NoError(t, err) {
return
}

assert.Equal(t, []byte("STDOUT: test_ok\n"), stdErrContent)
}

func TestOutCommandWithFilePlaceholder(t *testing.T) {
args := []string{"out", "/tmp"}
in := bytes.NewBufferString(`{
"source": {
"host": "localhost",
"user": "root",
"password": "toor"
},
"params": {
"interpreter": "/bin/sh",
"script": "echo <CODE_VERSION>",
"placeholders": [{
"name": "<CODE_VERSION>",
"file": "somefile"
}]
},
"version": { "ref": "0.5.0" }
}`)
out := bytes.NewBuffer([]byte{})
stdErr := bytes.NewBuffer([]byte{})

fs := afero.NewMemMapFs()
fs.MkdirAll("tmp", 0755)
afero.WriteFile(fs, "/tmp/somefile", []byte("first_line\nsecond_line\nthird_line"), 0644)

err := outCommand(fs, args, in, out, stdErr)
if !assert.NoError(t, err) {
return
}

stdErrContent, err := ioutil.ReadAll(stdErr)
if !assert.NoError(t, err) {
return
}

assert.Equal(t, []byte("STDOUT: first_line\n"), stdErrContent)
}
@@ -7,8 +7,9 @@ import (
"io/ioutil"
"testing"

"github.com/spf13/afero"

"github.com/icrowley/fake"
"github.com/reconquest/hierr-go"
"github.com/stretchr/testify/assert"

"github.com/henry40408/concourse-ssh-resource/internal/models"
@@ -33,10 +34,13 @@ func TestOutCommand(t *testing.T) {
return
}

args := []string{"out", "/tmp"}
in := bytes.NewBuffer(request)
out := bytes.NewBuffer([]byte{})
stdErr := bytes.NewBuffer([]byte{})
err = outCommand(in, out, stdErr)

fs := afero.NewMemMapFs()
err = outCommand(fs, args, in, out, stdErr)
if !assert.NoError(t, err) {
return
}
@@ -78,10 +82,13 @@ func TestOutCommandWithInterpreter(t *testing.T) {
return
}

args := []string{"out", "/tmp"}
in := bytes.NewBuffer(request)
out := bytes.NewBuffer([]byte{})
stdErr := bytes.NewBuffer([]byte{})
err = outCommand(in, out, stdErr)

fs := afero.NewMemMapFs()
err = outCommand(fs, args, in, out, stdErr)
if !assert.NoError(t, err) {
return
}
@@ -105,12 +112,14 @@ func TestOutCommandWithInterpreter(t *testing.T) {
}

func TestOutCommandWithMalformedJSON(t *testing.T) {
args := []string{"out", "/tmp"}
in := bytes.NewBufferString(`{`)
out := bytes.NewBuffer([]byte{})
stdErr := bytes.NewBuffer([]byte{})
err := outCommand(in, out, stdErr)
herr := err.(hierr.Error)
assert.Equal(t, herr.GetMessage(), "unable to parse JSON from standard input")

fs := afero.NewMemMapFs()
err := outCommand(fs, args, in, out, stdErr)
assert.Contains(t, err.Error(), "unable to parse JSON from standard input")
}

func TestOutCommandWithBadConnectionInfo(t *testing.T) {
@@ -129,11 +138,29 @@ func TestOutCommandWithBadConnectionInfo(t *testing.T) {
return
}

args := []string{"out", "/tmp"}
in := bytes.NewBuffer(request)
out := bytes.NewBuffer([]byte{})
stdErr := bytes.NewBuffer([]byte{})

err = outCommand(in, out, stdErr)
herr := err.(hierr.Error)
assert.Equal(t, herr.GetMessage(), "unable to run SSH command")
fs := afero.NewMemMapFs()
err = outCommand(fs, args, in, out, stdErr)
assert.Contains(t, err.Error(), "unable to run SSH command")
}

func TestOutCommandWithNoBaseDirectory(t *testing.T) {
args := []string{"out"}

in := bytes.NewBuffer([]byte{})
out := bytes.NewBuffer([]byte{})
stdErr := bytes.NewBuffer([]byte{})

fs := afero.NewMemMapFs()
err := outCommand(fs, args, in, out, stdErr)

if !assert.Error(t, err) {
return
}

assert.Contains(t, err.Error(), "need base directory, usage: out <base directory>")
}

0 comments on commit 69a3d93

Please sign in to comment.
You can’t perform that action at this time.