Skip to content
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
48 changes: 32 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Use bash for all recipes (GitHub runners + many systems default /bin/sh = dash, no pipefail)
SHELL := /usr/bin/env bash
.SHELLFLAGS := -euo pipefail -c

MODULE = $(shell go list -m)
VERSION ?= $(shell git describe --tags --always --dirty --match=v* 2> /dev/null || echo "1.0.0")
PACKAGES := $(shell go list ./... | grep -v /vendor/)
Expand All @@ -6,44 +10,55 @@ LDFLAGS := -ldflags "-X main.Version=${VERSION}"
# DSN is required only for DB-related targets (migrate/testdata), not for build/lint/etc.
APP_DSN ?=

# Fail only when DB targets are invoked (not on every make command)
PID_FILE := './.pid'
FSWATCH_FILE := './fswatch.cfg'

.PHONY: require-app-dsn
require-app-dsn:
@test -n "$(APP_DSN)" || (echo "APP_DSN is required. Set APP_DSN env var."; exit 1)

# IMPORTANT: use '=' (recursive expansion) so APP_DSN is evaluated at runtime
MIGRATE = docker run -v $(shell pwd)/migrations:/migrations --network host migrate/migrate:v4.10.0 \
MIGRATE = docker run --rm \
-v $(shell pwd)/migrations:/migrations \
--network host \
migrate/migrate:v4.19.1 \
-path=/migrations/ -database "$(APP_DSN)"

PID_FILE := './.pid'
FSWATCH_FILE := './fswatch.cfg'

.PHONY: default
default: help

# generate help info from comments: thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
.PHONY: help
help: ## help information about make commands
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: test
test: ## run unit tests
test: ## run unit tests (fails properly on test failure) and aggregate coverage
@echo "mode: count" > coverage-all.out
@$(foreach pkg,$(PACKAGES), \
go test -p=1 -cover -covermode=count -coverprofile=coverage.out ${pkg}; \
tail -n +2 coverage.out >> coverage-all.out;)
@rm -f coverage.out
@for pkg in $(PACKAGES); do \
echo "==> testing $$pkg"; \
go test -p=1 -cover -covermode=count -coverprofile=coverage.out "$$pkg"; \
tail -n +2 coverage.out >> coverage-all.out; \
done
@rm -f coverage.out

.PHONY: test-cover
test-cover: test ## run unit tests and show test coverage information
go tool cover -html=coverage-all.out
test-cover: test ## run unit tests and print coverage summary (CI-friendly)
@go tool cover -func=coverage-all.out | tail -n 1

.PHONY: cover-html
cover-html: test ## open HTML coverage report locally
@go tool cover -html=coverage-all.out

.PHONY: run
run: ## run the API server
go run ${LDFLAGS} cmd/server/main.go

.PHONY: run-restart
run-restart: ## restart the API server
@pkill -P `cat $(PID_FILE)` || true
@pkill -P "$$(cat $(PID_FILE) 2>/dev/null)" || true
@printf '%*s\n' "80" '' | tr ' ' -
@echo "Source file changed. Restarting server..."
@go run ${LDFLAGS} cmd/server/main.go & echo $$! > $(PID_FILE)
Expand All @@ -52,7 +67,8 @@ run-restart: ## restart the API server
.PHONY: run-live
run-live: ## run the API server with live reload support (requires fswatch)
@go run ${LDFLAGS} cmd/server/main.go & echo $$! > $(PID_FILE)
@fswatch -x -o --event Created --event Updated --event Renamed -r internal pkg cmd config | xargs -n1 -I {} make run-restart
@fswatch -x -o --event Created --event Updated --event Renamed -r internal pkg cmd config | \
xargs -n1 -I {} make run-restart

.PHONY: build
build: ## build the API server binary
Expand Down Expand Up @@ -83,12 +99,12 @@ db-stop: ## stop the database server

.PHONY: testdata
testdata: require-app-dsn ## populate the database with test data
make migrate-reset
@$(MAKE) migrate-reset
@echo "Populating test data..."
@docker exec -it postgres psql "$(APP_DSN)" -f /testdata/testdata.sql

.PHONY: lint
lint: ## run golint on all Go package
lint: ## run golint on all Go packages
@golint $(PACKAGES)

.PHONY: fmt
Expand Down
29 changes: 19 additions & 10 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"errors"
"fmt"
"github.com/go-ozzo/ozzo-validation/v4"
"github.com/ico12319/devops-project/pkg/log"
Expand Down Expand Up @@ -69,24 +70,32 @@ func Load(file string, logger log.Logger) (*Config, error) {
JWTExpiration: defaultJWTExpirationHours,
}

// load from YAML config file
bytes, err := readFileScoped(file)
if err != nil {
return nil, err
}
if err = yaml.Unmarshal(bytes, &c); err != nil {
return nil, err
// load from YAML config file (optional)
if file != "" {
bytes, err := readFileScoped(file)
if err != nil {
// If the file doesn't exist, ignore and rely on env.
if errors.Is(err, os.ErrNotExist) {
// continue
} else {
return nil, err
}
} else {
if err := yaml.Unmarshal(bytes, &c); err != nil {
return nil, err
}
}
}

// load from environment variables prefixed with "APP_"
if err = env.New("APP_", logger.Infof).Load(&c); err != nil {
if err := env.New("APP_", logger.Infof).Load(&c); err != nil {
return nil, err
}

// validation
if err = c.Validate(); err != nil {
if err := c.Validate(); err != nil {
return nil, err
}

return &c, err
return &c, nil
}
25 changes: 10 additions & 15 deletions internal/test/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,31 @@ import (
"github.com/ico12319/devops-project/pkg/dbcontext"
"github.com/ico12319/devops-project/pkg/log"
_ "github.com/lib/pq" // initialize posgresql for test
"path"
"runtime"
"testing"
)

var db *dbcontext.DB

// DB returns the database connection for testing purpose.
func DB(t *testing.T) *dbcontext.DB {
t.Helper()

if db != nil {
return db
}

logger, _ := log.NewForTest()
dir := getSourcePath()
cfg, err := config.Load(dir+"/../../config/local.yml", logger)

// Load config from env only (file optional / empty)
cfg, err := config.Load("", logger)
if err != nil {
t.Error(err)
t.FailNow()
t.Fatal(err)
}

dbc, err := dbx.MustOpen("postgres", cfg.DSN)
if err != nil {
t.Error(err)
t.FailNow()
t.Fatal(err)
}

dbc.LogFunc = logger.Infof
db = dbcontext.New(dbc)
return db
Expand All @@ -45,9 +46,3 @@ func ResetTables(t *testing.T, db *dbcontext.DB, tables ...string) {
}
}
}

// getSourcePath returns the directory containing the source code that is calling this function.
func getSourcePath() string {
_, filename, _, _ := runtime.Caller(1)
return path.Dir(filename)
}