Skip to content

Commit

Permalink
initial revision
Browse files Browse the repository at this point in the history
  • Loading branch information
johnmuth committed Sep 2, 2017
0 parents commit e2f3c6f
Show file tree
Hide file tree
Showing 289 changed files with 150,042 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
app
coverage/
.vscode
.DS_Store
.idea/
*.iml
*.iws
target/

9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM golang:1.8

WORKDIR /go/src/app
COPY . .

RUN go-wrapper download # "go get -d -v ./..."
RUN go-wrapper install # "go install -v ./..."

CMD ["go-wrapper", "run"] # ["app"]
43 changes: 43 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"fmt"
"net/http"
"os"
"time"
log "github.com/sirupsen/logrus"
"github.com/kelseyhightower/envconfig"
)

func main() {

config, err := LoadAppConfig()
if err != nil {
log.Error("Error loading config", err.Error())
}

log.Info("Listening on", config.Port)

httpClient := &http.Client{
// TODO: make these options configurable
Transport: &http.Transport{
MaxIdleConnsPerHost: 100,
},
Timeout: time.Millisecond * 1500,
}

service := &Service{config.ServiceBaseURL, httpClient}
handler := &Handler{*service}
err = http.ListenAndServe(fmt.Sprintf(":%d", config.Port), NewRouter(handler))

if err != nil {
log.Error("Problem starting server", err.Error())
os.Exit(1)
}
}

func LoadAppConfig() (*AppConfig, error) {
var config AppConfig
err := envconfig.Process("", &config)
return &config, err
}
11 changes: 11 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

type AppConfig struct {
Port int `default:"8000"`
ServiceBaseURL string `envconfig:"SERVICE_BASE_URL" required:"true"`
Env string `envconfig:"ENV_NAME" required:"true"`
}

func (c *AppConfig) IsLocal() bool {
return c.Env == "local"
}
21 changes: 21 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: '2'
services:
web:
build:
context: .
environment:
- SERVICE_BASE_URL=http://fake-service:9090/service
- ENV_NAME=local
- LOG_LEVEL=debug
links:
- fake-service
ports:
- "8000:8000"

fake-service:
image: quii/mockingjay-server:1.10.4
volumes:
- ./fakes:/fakes
command: -config=/fakes/fake-service.yml
ports:
- "9090:9090"
12 changes: 12 additions & 0 deletions fakes/fake-service.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
- name: fake service endpoint
cdcdisabled: false
request:
uri: /service
method: POST
headers:
content-type: application/json
body: '{"foo":"abc","bar":"def"}'
response:
code: 200
body: '{"qux":"flubber"}'
25 changes: 25 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"fmt"
log "github.com/sirupsen/logrus"
"net/http"
)

// Handler handles requests
type Handler struct {
Service Service
}

// ServeHTTP serves HTTP
func (handler Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
serviceRequest := &ServiceRequest{Foo:"abc", Bar:"def"}
serviceResponse, err := handler.Service.Call(*serviceRequest)
if err != nil {
log.Error("Error calling service", err)
w.WriteHeader(500)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, serviceResponse.String())
}
Binary file added http-client-test
Binary file not shown.
26 changes: 26 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"net/http"
"fmt"
)

func NewRouter(handler *Handler) http.Handler {

serveMux := http.NewServeMux()

// Add healthcheck handler

serveMux.HandleFunc("/internal/healthcheck", InternalHealthCheck)

// Add api handler
apiPath := "/api"
serveMux.Handle(apiPath, handler)
return serveMux

}

func InternalHealthCheck(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "Healthy")
}
11 changes: 11 additions & 0 deletions scripts/build-app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash -e

set -o errexit
set -o nounset
set -o pipefail

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

source "$SCRIPT_DIR/build-functions.sh"

buildAppCode
58 changes: 58 additions & 0 deletions scripts/build-functions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/bin/bash -e

function lintAppCode() {
gometalinter \
--vendor \
--exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \
--exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \
--exclude='duplicate of.*_test.go.*\(dupl\)$' \
--disable=aligncheck \
--disable=golint \
--disable=gotype \
--disable=structcheck \
--disable=varcheck \
--disable=unconvert \
--disable=aligncheck \
--disable=dupl \
--disable=goconst \
--disable=gosimple \
--disable=staticcheck \
--cyclo-over=20 \
--tests \
--deadline=30s
}

function fmtAppCode() {
(
cd "$PROJECT_BASE_DIR"
go fmt $(go list ./... | grep -v /vendor/)
)
}

function runTests() {
# We need to do a bit of fiddling to generate coverage data from multiple packages and merge them.
(
if [ ! -d coverage ]; then
mkdir coverage
fi
chmod 777 coverage
cd "$PROJECT_BASE_DIR"
echo "mode: count" > coverage/coverage-all.out
for pkg in $(go list ./... | grep -v /vendor/)
do
echo "pkg=$pkg"
richgo test -v -coverprofile=coverage/coverage.out -covermode=count $pkg
if [ -f coverage/coverage.out ]; then
tail -n +2 coverage/coverage.out >> coverage/coverage-all.out
fi
done
richgo test -v -tags=integration -coverprofile=coverage/coverage.out -covermode=count
tail -n +2 coverage/coverage.out >> coverage/coverage-all.out
go tool cover -html=coverage/coverage-all.out -o coverage/coverage.html
)
}

function buildAppCode() {
rm -f app
go build -v ./...
}
17 changes: 17 additions & 0 deletions scripts/docker-cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

# despite of calling `docker-compose build --force-rm` and
# `docker-compose run --rm` docker still produces dangling images and volumes
IMAGES_TO_REMOVE=$(docker images -qf dangling=true)
if [[ -n "$IMAGES_TO_REMOVE" ]]
then
echo "Removing dangling docker images..."
docker rmi -f $IMAGES_TO_REMOVE
fi

VOLUMES_TO_REMOVE=$(docker volume ls -qf dangling=true)
if [[ -n "$VOLUMES_TO_REMOVE" ]]
then
echo "Removing dangling docker volumes..."
docker volume rm $VOLUMES_TO_REMOVE
fi
4 changes: 4 additions & 0 deletions scripts/docker-run-locally.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

docker-compose down --remove-orphans
docker-compose up --build
5 changes: 5 additions & 0 deletions scripts/docker-run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash -e

docker-compose down --remove-orphans
docker-compose build web
docker-compose run web ./scripts/run-tests.sh
16 changes: 16 additions & 0 deletions scripts/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash -e

set -o errexit
set -o nounset
set -o pipefail
set -e
set -u

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"

source "$SCRIPT_DIR/build-functions.sh"

fmtAppCode
lintAppCode
runTests
50 changes: 50 additions & 0 deletions service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"errors"
log "github.com/sirupsen/logrus"
)

// Service sends requests to a remote HTTP API
type Service struct {
BaseURL string
HttpClient HttpClient
}

type HttpClient interface {
Post(url string, bodyType string, body io.Reader) (*http.Response, error)
}

func (svc Service) Call(serviceRequest ServiceRequest) (serviceResponse ServiceResponse, err error) {
serviceRequestURL := fmt.Sprintf("%s", svc.BaseURL)
var resp *http.Response
log.Info(fmt.Sprintf("About to send request: POST %s with body %s", serviceRequestURL, serviceRequest.String()))
resp, err = svc.HttpClient.Post(serviceRequestURL, "application/json", strings.NewReader(serviceRequest.String()))
if err != nil {
log.Error("Error sending request to service", err)
return
}
if resp.StatusCode != 200 {
var respBody string
if resp.Body != nil {
respErrorBody, _ := ioutil.ReadAll(resp.Body)
respBody = string(respErrorBody)
resp.Body.Close()
}
errorMsg := fmt.Sprintf("Request to service returned status: %d and body: %s ", resp.StatusCode, respBody)
log.Error(errorMsg)
return serviceResponse, errors.New(errorMsg)
}
err = json.NewDecoder(resp.Body).Decode(&serviceResponse)
resp.Body.Close()
if err != nil {
log.Error("Error parsing response from Service.", err)
}
return
}
15 changes: 15 additions & 0 deletions servicerequest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import (
"encoding/json"
)

type ServiceRequest struct {
Foo string `json:"foo,omitempty"`
Bar string `json:"bar,omitempty"`
}

func (req ServiceRequest) String() string {
out, _ := json.Marshal(req)
return string(out)
}
14 changes: 14 additions & 0 deletions serviceresponse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

import (
"encoding/json"
)

type ServiceResponse struct {
Qux string `json:"qux"`
}

func (sr ServiceResponse) String() string {
out, _ := json.Marshal(sr)
return string(out)
}
19 changes: 19 additions & 0 deletions vendor/github.com/kelseyhightower/envconfig/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions vendor/github.com/kelseyhightower/envconfig/MAINTAINERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e2f3c6f

Please sign in to comment.