feat: add skaffold.yml (#9)
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: twhelp/core#9
This commit is contained in:
Dawid Wysokiński 2022-07-28 05:35:07 +00:00
parent 602f5456bc
commit 682939a3cf
13 changed files with 311 additions and 6 deletions

10
README.md Normal file
View File

@ -0,0 +1,10 @@
# TWHelp
## Local development
**Prerequisites:**
1. Kubernetes
2. kustomize
3. helm
4. skaffold

View File

@ -0,0 +1,18 @@
FROM golang:1.18 as builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
ARG SKAFFOLD_GO_GCFLAGS
RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o twhelp ./cmd/twhelp/main.go
FROM ubuntu:22.04
# Define GOTRACEBACK to mark this container as using the Go language runtime
# for `skaffold debug` (https://skaffold.dev/docs/workflows/debug/).
WORKDIR /root
ENV GOTRACEBACK=single
RUN apt update
RUN apt install -y ca-certificates tzdata
COPY --from=builder /app/twhelp .
ENTRYPOINT ["./twhelp"]

View File

@ -21,7 +21,7 @@ const (
defaultConnPerCpu = 10
defaultConnLifetime = "3m"
defaultConnIdleTime = "5m"
maxOpenConnsDivider = 2
maxOpenConnsDivisor = 2
dbPingTimeout = 10 * time.Second
dbTypePostgres = "postgres"
)
@ -34,7 +34,7 @@ func NewBunDB() (*bun.DB, error) {
maxIdleConnections, _ := getenvInt("DB_MAX_IDLE_CONNECTIONS")
if maxIdleConnections == 0 {
maxIdleConnections = maxOpenConnections / maxOpenConnsDivider
maxIdleConnections = maxOpenConnections / maxOpenConnsDivisor
}
maxConnectionLifetimeStr, _ := getenvString("DB_MAX_CONNECTION_LIFETIME")

View File

@ -0,0 +1,143 @@
package serve
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"gitea.dwysokinski.me/twhelp/core/internal/rest"
"gitea.dwysokinski.me/twhelp/core/internal/bundb"
"gitea.dwysokinski.me/twhelp/core/internal/service"
"github.com/uptrace/bun"
"github.com/go-chi/cors"
"github.com/Kichiyaki/appmode/v2"
"github.com/Kichiyaki/chizap"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
"gitea.dwysokinski.me/twhelp/core/cmd/twhelp/internal"
"github.com/urfave/cli/v2"
)
const (
defaultPort = "9234"
readTimeout = 2 * time.Second
readHeaderTimeout = 2 * time.Second
writeTimeout = 2 * time.Second
idleTimeout = 2 * time.Second
serverShutdownTimeout = 10 * time.Second
corsMaxAge = 300
)
func New() *cli.Command {
return &cli.Command{
Name: "serve",
Usage: "Runs the http server",
Action: func(c *cli.Context) error {
db, err := internal.NewBunDB()
if err != nil {
return fmt.Errorf("internal.NewBunDB: %w", err)
}
defer func() {
_ = db.Close()
}()
logger := zap.L()
srv, err := newServer(serverConfig{
logger: logger,
db: db,
})
if err != nil {
return fmt.Errorf("newServer: %w", err)
}
go startServer(srv, logger)
logger.Info("Server is listening on the port "+defaultPort, zap.String("port", defaultPort))
waitForSignal(context.Background())
ctxShutdown, cancelShutdown := context.WithTimeout(context.Background(), serverShutdownTimeout)
defer cancelShutdown()
if err := srv.Shutdown(ctxShutdown); err != nil {
return fmt.Errorf("srv.Shutdown: %w", err)
}
return nil
},
}
}
type serverConfig struct {
logger *zap.Logger
db *bun.DB
}
func newServer(cfg serverConfig) (*http.Server, error) {
// repos
versionRepo := bundb.NewVersion(cfg.db)
// services
versionSvc := service.NewVersion(versionRepo)
// router
r := chi.NewRouter()
r.Use(getChiMiddlewares(cfg.logger)...)
rest.NewVersion(versionSvc).Register(r)
return &http.Server{
Addr: ":" + defaultPort,
Handler: r,
ReadTimeout: readTimeout,
ReadHeaderTimeout: readHeaderTimeout,
WriteTimeout: writeTimeout,
IdleTimeout: idleTimeout,
}, nil
}
func getChiMiddlewares(logger *zap.Logger) chi.Middlewares {
mws := chi.Middlewares{
middleware.RealIP,
chizap.Logger(logger),
middleware.Recoverer,
middleware.Heartbeat("/health"),
}
if appmode.Equal(appmode.DevelopmentMode) {
mws = append(mws, newCORSMiddlewareForDevMode())
}
return mws
}
func newCORSMiddlewareForDevMode() func(next http.Handler) http.Handler {
return cors.Handler(cors.Options{
AllowOriginFunc: func(*http.Request, string) bool {
return true
},
AllowCredentials: true,
ExposedHeaders: []string{"Authorization"},
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"},
AllowedHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"},
MaxAge: corsMaxAge,
})
}
func startServer(srv *http.Server, logger *zap.Logger) {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Fatal("srv.ListenAndServe:" + err.Error())
}
}
func waitForSignal(ctx context.Context) {
ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done()
}

View File

@ -4,6 +4,8 @@ import (
"log"
"os"
"gitea.dwysokinski.me/twhelp/core/cmd/twhelp/internal/serve"
"gitea.dwysokinski.me/twhelp/core/cmd/twhelp/internal"
"gitea.dwysokinski.me/twhelp/core/cmd/twhelp/internal/db"
"github.com/urfave/cli/v2"
@ -38,6 +40,7 @@ func newApp() *cli.App {
app.EnableBashCompletion = true
app.Commands = []*cli.Command{
db.New(),
serve.New(),
}
return app
}

6
go.mod
View File

@ -4,8 +4,10 @@ go 1.18
require (
github.com/Kichiyaki/appmode/v2 v2.0.1
github.com/Kichiyaki/chizap v0.1.1
github.com/elliotchance/phpserialize v1.3.3
github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/cors v1.2.1
github.com/google/go-cmp v0.5.5
github.com/ory/dockertest/v3 v3.9.1
github.com/stretchr/testify v1.8.0
@ -48,8 +50,8 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect

10
go.sum
View File

@ -3,6 +3,8 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Kichiyaki/appmode/v2 v2.0.1 h1:KUjxgSwlWUfV6n9HFCsBZNdlLOuGCPF26eXJphLS9is=
github.com/Kichiyaki/appmode/v2 v2.0.1/go.mod h1:zO9n0/t5BApADvRu4+GpK8rWBatXyjtObQuvXGLSqKk=
github.com/Kichiyaki/chizap v0.1.1 h1:8GacY8tPwoYMdSqwzy9IfPXHgbwzK3RnORqAA8FKQEc=
github.com/Kichiyaki/chizap v0.1.1/go.mod h1:PJpJQVJyJmtDAKPwT7rdWuscezD2T1hnU/Yj25PLVSs=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
@ -39,6 +41,8 @@ github.com/elliotchance/phpserialize v1.3.3/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lc
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@ -133,12 +137,14 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=

54
k8s/base/api.yml Normal file
View File

@ -0,0 +1,54 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: twhelp-api-deployment
spec:
selector:
matchLabels:
app: twhelp-api
template:
metadata:
labels:
app: twhelp-api
spec:
containers:
- name: twhelp-api
image: twhelp
args: ["serve"]
ports:
- name: container-port
containerPort: 9234
env:
- name: APP_MODE
value: development
- name: DB_DSN
valueFrom:
secretKeyRef:
name: twhelp-secret
key: db-dsn
- name: DB_MAX_OPEN_CONNECTIONS
value: "10"
livenessProbe:
httpGet:
path: /health
port: container-port
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
tcpSocket:
port: container-port
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: twhelp-api-service
spec:
ports:
- port: 9234
targetPort: 9234
protocol: TCP
name: http
selector:
app: twhelp-api

View File

@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- migrations.yml
- api.yml
images:
- name: twhelp
newName: twhelp
newTag: latest

20
k8s/base/migrations.yml Normal file
View File

@ -0,0 +1,20 @@
apiVersion: batch/v1
kind: Job
metadata:
name: twhelp-migrations-job
spec:
template:
spec:
containers:
- name: twhelp-migrations
image: twhelp
args: ["db", "migrate"]
env:
- name: DB_MAX_OPEN_CONNECTIONS
value: "1"
- name: DB_DSN
valueFrom:
secretKeyRef:
name: twhelp-secret
key: db-dsn
restartPolicy: Never

View File

@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
nameSuffix: -dev
resources:
- secret.yml
- ../../base

View File

@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: twhelp-secret
type: Opaque
data:
# postgres://twhelp:twhelp@twhelpdb-postgresql:5432/twhelp?sslmode=disable
db-dsn: cG9zdGdyZXM6Ly90d2hlbHA6dHdoZWxwQHR3aGVscGRiLXBvc3RncmVzcWw6NTQzMi90d2hlbHA/c3NsbW9kZT1kaXNhYmxl

26
skaffold.yml Normal file
View File

@ -0,0 +1,26 @@
apiVersion: skaffold/v2beta27
kind: Config
build:
tagPolicy:
customTemplate:
template: latest
artifacts:
- image: twhelp
context: .
docker:
dockerfile: ./build/docker/twhelp/dev/Dockerfile
deploy:
helm:
releases:
- name: twhelpdb
repo: https://charts.bitnami.com/bitnami
remoteChart: postgresql
version: 11.6.19
wait: true
setValues:
auth.username: twhelp
auth.password: twhelp
auth.database: twhelp
kustomize:
paths:
- k8s/overlays/dev