Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor to idiomatic go #9

Merged
2 commits merged into from
Nov 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
MAIN_VERSION:=$(shell git describe --always || echo "1.0")
VERSION:=${MAIN_VERSION}\#$(shell git log -n 1 --pretty=format:"%h")
PACKAGES:=$(shell go list ./... | sed -n '1!p' | grep -v -e /vendor/ -e github.com/ch-robinson/vault-elastic-plugin/plugin/interfaces)
LDFLAGS:=-ldflags "-X github.com/ch-robinson/vault-elastic-plugin/plugin.Version=${VERSION}"
PACKAGES:=$(shell go list ./... | sed -n '1!p' | grep -v -e /vendor/)
LDFLAGS:=-ldflags "-X github.com/ch-robinson/vault-elastic-plugin/main.Version=${VERSION}"

ifeq ($(OS),Windows_NT)
DETECTED_OS := Windows
Expand All @@ -20,14 +20,6 @@ default: test

test:
@echo "mode: count" > coverage-all.out
@echo
@echo "************** SKIPPING TESTS IN PACKAGES ****************"
@echo
@echo "skipping github.com/ch-robinson/vault-elastic-plugin/plugin/interfaces"
@echo
@echo "**********************************************************"
@echo

@$(foreach pkg,$(PACKAGES), \
go test -p=1 -cover -covermode=count -coverprofile=coverage.out ${pkg} || exit 1; \
tail -n +2 coverage.out >> coverage-all.out;)
Expand Down
16 changes: 8 additions & 8 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# vault-elastic-plugin

### Setup
## Setup

*** NOTE: For Mac or linux, make should be installed by default so you can skip steps 2 and 3 ***

Expand All @@ -12,33 +12,33 @@
- Mac - ```brew install glide```
5. ```go get github.com/ch-robinson/vault-elastic-plugin```

### Testing locally
## Testing locally

1. Download [Vault](https://www.vaultproject.io/downloads.html) and extract the compressed file to the location of your choosing
2. Add Vault to path
3. In a new terminal run Vault: ```make run-vault```
4. In a new terminal run:
4. In a new terminal run:
- With build and vault DB configuration: ```make test-plugin ELASTIC_BASE_URI=<uri> ELASTIC_PASSWORD=<password> ELASTIC_USERNAME=<username> INCLUDE_BUILD=true ENABLE_VAULT_DB=true```
- Without build: ```make test-plugin ELASTIC_BASE_URI=<uri> ELASTIC_PASSWORD=<password> ELASTIC_USERNAME=<username> INCLUDE_BUILD=false ENABLE_VAULT_DB=true```

### Build
## Build

- Unix based: ```make build```
- Powershell (if make is not in path): ```C:\Program Files (x86)\GnuWin32\bin\make.exe build```
- bash for Windows (if make is not in path): ```/c/Program\ Files\ \(x86\)/GnuWin32/bin/make.exe build```

The executable binary is located ../bin/run

### Unit Testing
## Unit Testing

At the root of the project, run

At the root of the project, run
- Unix based: ```make test```
- Powershell (if make is not in path): ```C:\Program Files (x86)\GnuWin32\bin\make.exe test```
- bash for Windows (if make is not in path): ```/c/Program\ Files\ \(x86\)/GnuWin32/bin/make.exe test```

### Dependencies
## Dependencies

- Unix based: ```make depends```
- Powershell (if make is not in path): ```C:\Program Files (x86)\GnuWin32\bin\make.exe depends```
- bash for Windows (if make is not in path): ```/c/Program\ Files\ \(x86\)/GnuWin32/bin/make.exe depends```

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"
"sync"

"github.com/ch-robinson/vault-elastic-plugin/plugin/interfaces"
"github.com/ch-robinson/vault-elastic-plugin/httputil"
"github.com/mitchellh/mapstructure"
)

Expand All @@ -17,7 +17,7 @@ type connectionProducer struct {
Password string `json:"password" structs:"password" mapstructure:"password"`
RawConfig map[string]interface{}
Type string
HTTPClient interfaces.IHTTPClient
HTTPClient httputil.ClientWrapperer
sync.RWMutex
}

Expand Down
8 changes: 4 additions & 4 deletions plugin/elastic/elastic_db.go → elastic/elastic_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"fmt"
"time"

"github.com/ch-robinson/vault-elastic-plugin/plugin/interfaces"
"github.com/ch-robinson/vault-elastic-plugin/httputil"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/logical/database/dbplugin"
"github.com/hashicorp/vault/plugins/helper/database/credsutil"
Expand All @@ -24,7 +24,7 @@ type Database struct {
var _ dbplugin.Database = &Database{}

// New returns a new Elastic instance with provided implementation of http.Client
func New(httpClient interfaces.IHTTPClient) (interface{}, error) {
func New(httpClient httputil.ClientWrapperer) (interface{}, error) {
// setup struct
db := &Database{
connectionProducer: &connectionProducer{
Expand All @@ -47,7 +47,7 @@ func New(httpClient interfaces.IHTTPClient) (interface{}, error) {
}

// Run instantiates the Database struct, and runs the RPC server for the plugin
func Run(serve func(plugin interface{}, tlsConfig *api.TLSConfig), apiTLSConfig *api.TLSConfig, httpClient interfaces.IHTTPClient) error {
func Run(serve func(plugin interface{}, tlsConfig *api.TLSConfig), apiTLSConfig *api.TLSConfig, httpClient httputil.ClientWrapperer) error {
dbType, err := New(httpClient)

if err != nil {
Expand Down Expand Up @@ -155,7 +155,7 @@ func (m *Database) RevokeUser(ctx context.Context, statements dbplugin.Statement
// RotateRootCredentials rotates the root superuser credentials stored for the database connection
func (m *Database) RotateRootCredentials(ctx context.Context, statements []string) (map[string]interface{}, error) {
if len(m.Username) == 0 || len(m.Password) == 0 {
return nil, errors.New("Both the username and password are required.")
return nil, errors.New("both the username and password are required")
}

password, err := m.GeneratePassword()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func TestRotateRootCredentialsFailWithoutConnectionCredentials(t *testing.T) {

_, err := db.RotateRootCredentials(testdata.NewMockVaultContext(), []string{})

assert.Equal(t, "Both the username and password are required.", err.Error())
assert.Equal(t, "both the username and password are required", err.Error())
}

func TestRotateRootCredentialsFailOnBadUsername(t *testing.T) {
Expand Down
108 changes: 108 additions & 0 deletions httputil/http_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package httputil

import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
)

// HTTP is the interface for http.Client
// https://golang.org/pkg/net/http/
type httpWrapper interface {
// Do wraps the http.Client Do function
Do(req *http.Request) (*http.Response, error)
}

// ClientWrapperer is the interface for functions relating to building http request
type ClientWrapperer interface {
// Do peforms an http request. This is just a wrapper for the http.Client function
// calls HTTP.Do for ease of testing
Do(req *http.Request) (*http.Response, error)
// BuildBasicAuthRequest creates an http.Request with basic authoriztion header.
// body must be map[string]interface{}
BuildBasicAuthRequest(requestURL, username, password, httpMethod string, body map[string]interface{}) (*http.Request, error)
// ReadHTTPResponse returns the response body as map[string]interface{}
ReadHTTPResponse(res *http.Response) (map[string]interface{}, error)
}

// ClientWrapper is the wrapper for interacting with http methods
type ClientWrapper struct {
client httpWrapper
}

// New instantiates a new ClientWrapper
func New(client httpWrapper) *ClientWrapper {
return &ClientWrapper{client}
}

// Do peforms an http request
func (c *ClientWrapper) Do(req *http.Request) (*http.Response, error) {
return c.client.Do(req)
}

// BuildBasicAuthRequest creates an http.Request with basic authoriztion header.
// body must be map[string]interface{}
func (c *ClientWrapper) BuildBasicAuthRequest(requestURL, username, password, httpMethod string, body map[string]interface{}) (*http.Request, error) {
var req *http.Request
var err error

if body != nil && len(body) > 0 {
reqBody, err := json.Marshal(body)
if err != nil {
return nil, err
}

recloser := NewClosingBuffer(bytes.NewBuffer(reqBody)).GetReadCloser().(*ClosingBuffer)

if err != nil {
return nil, err
}

req, err = http.NewRequest(httpMethod, requestURL, recloser)
} else {
req, err = http.NewRequest(httpMethod, requestURL, nil)
}

req.SetBasicAuth(username, password)

if err != nil {
return nil, err
}

c.addHeaders(&req.Header)

return req, nil
}

// ReadHTTPResponse returns the response body as map[string]interface{}
func (c *ClientWrapper) ReadHTTPResponse(res *http.Response) (map[string]interface{}, error) {
resBody, err := ioutil.ReadAll(res.Body)

defer res.Body.Close()

if err != nil {
return nil, err
}

var body map[string]interface{}

err = json.Unmarshal(resBody, &body)

if err != nil {
return nil, err
}

// Throw error if not ok. Might need to watch out for other success codes, but this should be ok.
if res.StatusCode != 200 {
return nil, err
}

return body, nil
}

// addHeaders adds http.Headers. If accessToken is provided, an Authorization header will be added with given authType (Bearer, token, etc.)
func (c *ClientWrapper) addHeaders(header *http.Header) {
header.Add("Content-Type", "application/json")
header.Add("Accept", "application/json")
}
34 changes: 17 additions & 17 deletions plugin/util/http_client_test.go → httputil/http_client_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package util
package httputil

import (
"bytes"
Expand All @@ -9,22 +9,22 @@ import (
"github.com/stretchr/testify/assert"
)

func initHTTPClient(resBody *string) *HTTPClient {
func initClient(resBody *string) *ClientWrapper {
mockHTTP := testdata.NewMockHTTP(resBody)
httpClient := NewHTTPClient(mockHTTP)
return httpClient
clientWrapper := New(mockHTTP)
return clientWrapper
}

func TestNewHTTPClient(t *testing.T) {
func TestNewClient(t *testing.T) {
mockHTTP := testdata.NewMockHTTP(nil)

httpClient := NewHTTPClient(mockHTTP)
clientWrapper := New(mockHTTP)

assert.Equal(t, mockHTTP, httpClient.client)
assert.Equal(t, mockHTTP, clientWrapper.client)
}

func TestReadHTTPResponse(t *testing.T) {
httpClient := initHTTPClient(nil)
clientWrapper := initClient(nil)

readCloser := NewClosingBuffer(bytes.NewBufferString("{\"test\":\"body\"}")).GetReadCloser()

Expand All @@ -34,49 +34,49 @@ func TestReadHTTPResponse(t *testing.T) {
Body: readCloser,
}

response, err := httpClient.ReadHTTPResponse(res)
response, err := clientWrapper.ReadHTTPResponse(res)

assert.Nil(t, err)
assert.True(t, response != nil)
}

func TestAddHeaders(t *testing.T) {
httpClient := initHTTPClient(nil)
clientWrapper := initClient(nil)
var h http.Header = make(map[string][]string)

httpClient.addHeaders(&h)
clientWrapper.addHeaders(&h)

assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, "application/json", h.Get("Accept"))
}

func TestDo(t *testing.T) {
b := "good"
httpClient := initHTTPClient(&b)
clientWrapper := initClient(&b)

req, _ := http.NewRequest("GET", "mocked", nil)

_, err := httpClient.Do(req)
_, err := clientWrapper.Do(req)

assert.Nil(t, err)
}

func TestBuildBasicAuthRequestWithBody(t *testing.T) {
httpClient := initHTTPClient(nil)
clientWrapper := initClient(nil)
body := make(map[string]interface{})
body["test"] = true

req, err := httpClient.BuildBasicAuthRequest("http://test", "testuser", "testpassword", "POST", body)
req, err := clientWrapper.BuildBasicAuthRequest("http://test", "testuser", "testpassword", "POST", body)

assert.Nil(t, err)
assert.NotNil(t, req)
assert.Equal(t, "Basic dGVzdHVzZXI6dGVzdHBhc3N3b3Jk", req.Header.Get("Authorization"))
}

func TestBuildBasicAuthRequestNilBody(t *testing.T) {
httpClient := initHTTPClient(nil)
clientWrapper := initClient(nil)

req, err := httpClient.BuildBasicAuthRequest("http://test", "testuser", "testpassword", "DELETE", nil)
req, err := clientWrapper.BuildBasicAuthRequest("http://test", "testuser", "testpassword", "DELETE", nil)

assert.Nil(t, err)
assert.NotNil(t, req)
Expand Down
2 changes: 1 addition & 1 deletion plugin/util/read_closer.go → httputil/read_closer.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package util
package httputil

import (
"bytes"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package util
package httputil

import (
"bytes"
Expand Down
8 changes: 5 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ import (
"net/http"
"os"

"github.com/ch-robinson/vault-elastic-plugin/plugin/elastic"
"github.com/ch-robinson/vault-elastic-plugin/plugin/util"
"github.com/ch-robinson/vault-elastic-plugin/elastic"
"github.com/ch-robinson/vault-elastic-plugin/httputil"
"github.com/hashicorp/vault/helper/pluginutil"
"github.com/hashicorp/vault/plugins"
)

var Version = "1.0"

func main() {
apiClientMeta := &pluginutil.APIClientMeta{}
flags := apiClientMeta.FlagSet()
flags.Parse(os.Args[1:])

clientWrapper := util.NewHTTPClient(&http.Client{})
clientWrapper := httputil.New(&http.Client{})

if err := elastic.Run(plugins.Serve, apiClientMeta.GetTLSConfig(), clientWrapper); err != nil {
log.Println(err)
Expand Down
22 changes: 0 additions & 22 deletions plugin/interfaces/http.go

This file was deleted.

Loading