From 682939a3cff04a3e4f32239d14aff624a1795149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Thu, 28 Jul 2022 05:35:07 +0000 Subject: [PATCH] feat: add skaffold.yml (#9) Reviewed-on: https://gitea.dwysokinski.me/twhelp/core/pulls/9 --- README.md | 10 ++ build/docker/twhelp/dev/Dockerfile | 18 ++++ cmd/twhelp/internal/bun.go | 4 +- cmd/twhelp/internal/serve/serve.go | 143 +++++++++++++++++++++++++++++ cmd/twhelp/main.go | 3 + go.mod | 6 +- go.sum | 10 +- k8s/base/api.yml | 54 +++++++++++ k8s/base/kustomization.yml | 9 ++ k8s/base/migrations.yml | 20 ++++ k8s/overlays/dev/kustomization.yml | 6 ++ k8s/overlays/dev/secret.yml | 8 ++ skaffold.yml | 26 ++++++ 13 files changed, 311 insertions(+), 6 deletions(-) create mode 100644 README.md create mode 100644 build/docker/twhelp/dev/Dockerfile create mode 100644 cmd/twhelp/internal/serve/serve.go create mode 100644 k8s/base/api.yml create mode 100644 k8s/base/kustomization.yml create mode 100644 k8s/base/migrations.yml create mode 100644 k8s/overlays/dev/kustomization.yml create mode 100644 k8s/overlays/dev/secret.yml create mode 100644 skaffold.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e264c0 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# TWHelp + +## Local development + +**Prerequisites:** +1. Kubernetes +2. kustomize +3. helm +4. skaffold + diff --git a/build/docker/twhelp/dev/Dockerfile b/build/docker/twhelp/dev/Dockerfile new file mode 100644 index 0000000..77dff0e --- /dev/null +++ b/build/docker/twhelp/dev/Dockerfile @@ -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"] diff --git a/cmd/twhelp/internal/bun.go b/cmd/twhelp/internal/bun.go index a922c57..93bd6f0 100644 --- a/cmd/twhelp/internal/bun.go +++ b/cmd/twhelp/internal/bun.go @@ -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") diff --git a/cmd/twhelp/internal/serve/serve.go b/cmd/twhelp/internal/serve/serve.go new file mode 100644 index 0000000..41dd40e --- /dev/null +++ b/cmd/twhelp/internal/serve/serve.go @@ -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() +} diff --git a/cmd/twhelp/main.go b/cmd/twhelp/main.go index 8fac348..667d52c 100644 --- a/cmd/twhelp/main.go +++ b/cmd/twhelp/main.go @@ -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 } diff --git a/go.mod b/go.mod index 1da86b5..963bc67 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 3c1de84..17820ad 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/k8s/base/api.yml b/k8s/base/api.yml new file mode 100644 index 0000000..86b3649 --- /dev/null +++ b/k8s/base/api.yml @@ -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 diff --git a/k8s/base/kustomization.yml b/k8s/base/kustomization.yml new file mode 100644 index 0000000..1683bf5 --- /dev/null +++ b/k8s/base/kustomization.yml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - migrations.yml + - api.yml +images: + - name: twhelp + newName: twhelp + newTag: latest diff --git a/k8s/base/migrations.yml b/k8s/base/migrations.yml new file mode 100644 index 0000000..ace4bd2 --- /dev/null +++ b/k8s/base/migrations.yml @@ -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 diff --git a/k8s/overlays/dev/kustomization.yml b/k8s/overlays/dev/kustomization.yml new file mode 100644 index 0000000..d0601d7 --- /dev/null +++ b/k8s/overlays/dev/kustomization.yml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +nameSuffix: -dev +resources: + - secret.yml + - ../../base diff --git a/k8s/overlays/dev/secret.yml b/k8s/overlays/dev/secret.yml new file mode 100644 index 0000000..7ada084 --- /dev/null +++ b/k8s/overlays/dev/secret.yml @@ -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 diff --git a/skaffold.yml b/skaffold.yml new file mode 100644 index 0000000..4eabdcf --- /dev/null +++ b/skaffold.yml @@ -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