Skip to content

Commit 7480245

Browse files
authored
Merge pull request #88 from mutablelogic/v5
V5
2 parents 9596f9d + 625b573 commit 7480245

File tree

15 files changed

+446
-81
lines changed

15 files changed

+446
-81
lines changed

Makefile

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ $(CMD_DIR): go-dep mkdir
5757
@echo Build command $(notdir $@) GOOS=${OS} GOARCH=${ARCH}
5858
@GOOS=${OS} GOARCH=${ARCH} ${GO} build ${BUILD_FLAGS} -o ${BUILD_DIR}/$(notdir $@) ./$@
5959

60+
# Build the plugins
61+
.PHONY: plugins
62+
plugins: $(NPM_DIR) $(PLUGIN_DIR)
63+
6064
# Build the plugins
6165
$(PLUGIN_DIR): go-dep mkdir
6266
@echo Build plugin $(notdir $@) GOOS=${OS} GOARCH=${ARCH}
@@ -65,7 +69,7 @@ $(PLUGIN_DIR): go-dep mkdir
6569
# Build the NPM packages
6670
$(NPM_DIR): npm-dep
6771
@echo Build npm $(notdir $@)
68-
@cd $@ && npm install && npm run prod
72+
@cd $@ && npm install && npm run dist
6973

7074
# Build the docker image
7175
.PHONY: docker
@@ -120,9 +124,13 @@ mkdir:
120124

121125
.PHONY: clean
122126
clean:
123-
@echo Clean
127+
@echo Clean $(BUILD_DIR)
124128
@rm -fr $(BUILD_DIR)
125129
@${GO} clean
130+
@for dir in $(NPM_DIR); do \
131+
echo "Cleaning npm $$dir"; \
132+
cd $$dir && ${NPM} run clean; \
133+
done
126134

127135
###############################################################################
128136
# DEPENDENCIES

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# go-server
2+
3+
This is an opinionated HTTP server which is designed to be used as a base for building
4+
API and web applications. It is designed to be extensible and modular, allowing you to add
5+
new features and functionality as needed. By default the server includes the following
6+
features:
7+
8+
* Authentication of users using JWT tokens and API keys
9+
* Connection to PostgreSQL databases
10+
* Task queues for running background jobs
11+
* Ability to manage the PostgreSQL database roles, databases, schemas and connections
12+
* Prometheus metrics support
13+
14+
The idea is that you can use this server as a base for your own applications, and add your own
15+
features and functionality as needed. More documentation soon on how to do that.
16+
17+
## Running
18+
19+
You can run the server in a docker container or build it from source. To run the latest
20+
released version as a docker container:
21+
22+
```bash
23+
docker run ghcr.io/mutablelogic/go-server:latest
24+
```
25+
26+
This will print out the help message.
27+
28+
## Building
29+
30+
### Download and build
31+
32+
```bash
33+
git clone github.com/mutablelogic/go-server
34+
cd go-server
35+
make
36+
```
37+
38+
The plugins and the `server` binary will be built in the `build` directory.
39+
40+
### Build requirements
41+
42+
You need the following three tools installed to build the server:
43+
44+
- [Go](https://golang.org/doc/install/source) (1.23 or later, not required for docker builds)
45+
- [Make](https://www.gnu.org/software/make/)
46+
- [Docker](https://docs.docker.com/get-docker/)
47+
- [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
48+
49+
### Makefile targets
50+
51+
Binaries are placed in the `build` directory.
52+
53+
| Target | Description |
54+
|--------|-------------|
55+
| `make all` | Build all plugins and the server |
56+
| `make cmd/server` | Build the server binary |
57+
| `make plugins` | Build the server binary |
58+
| `make docker` | Build the docker container |
59+
| `make docker-push` | Push the docker container to remote repository, assuming logged in |
60+
| `make docker-version` | Prints out the docker container tag |
61+
| `make test` | Runs unit amd coverage tests |
62+
| `make unit-test` | Runs unit tests |
63+
| `VERBOSE=1 make unit-test` | Runs unit tests with verbose output |
64+
| `make coverage-test` | Reports code coverage |
65+
| `make tidy` | Runs go mod tidy |
66+
| `make clean` | Removes binaries and distribution files |
67+
68+
You can also affect the build by setting the following environment variables. For example,
69+
70+
```bash
71+
GOOS=linux GOARCH=amd64 make
72+
```
73+
74+
| Variable | Description |
75+
|----------|-------------|
76+
| `GOOS` | The target operating system for the build |
77+
| `GOARCH` | The target architecture for the build |
78+
| `BUILD_DIR` | The target architecture for the build |
79+
| `VERBOSE` | Setting this flag will provide verbose output for unit tests |
80+
| `VERSION` | Explicitly set the version |
81+
| `DOCKER_REPO` | The docker repository to push to. Defaults to `ghcr.io/mutablelogic/go-server` |
82+

cmd/server/service.go

Lines changed: 177 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package main
22

33
import (
44
"context"
5-
"fmt"
5+
"errors"
66
"net/http"
7+
"os"
78

89
// Packages
910
server "github.com/mutablelogic/go-server"
11+
helloworld "github.com/mutablelogic/go-server/npm/helloworld"
1012
httpresponse "github.com/mutablelogic/go-server/pkg/httpresponse"
1113
provider "github.com/mutablelogic/go-server/pkg/provider"
1214
ref "github.com/mutablelogic/go-server/pkg/ref"
@@ -20,23 +22,186 @@ import (
2022
logger "github.com/mutablelogic/go-server/pkg/logger/config"
2123
pg "github.com/mutablelogic/go-server/pkg/pgmanager/config"
2224
pgqueue "github.com/mutablelogic/go-server/pkg/pgqueue/config"
23-
24-
// Static content
25-
helloworld "github.com/mutablelogic/go-server/npm/helloworld"
2625
)
2726

2827
///////////////////////////////////////////////////////////////////////////////
2928
// TYPES
3029

3130
type ServiceCommands struct {
32-
Run ServiceRunCommand `cmd:"" group:"SERVICE" help:"Run the service"`
31+
// Run ServiceRunCommand `cmd:"" group:"SERVICE" help:"Run the service"`
32+
Run ServiceRunCommand `cmd:"" group:"SERVICE" help:"Run the service with plugins"`
33+
Config ServiceConfigCommand `cmd:"" group:"SERVICE" help:"Output the plugin configuration"`
3334
}
3435

3536
type ServiceRunCommand struct {
36-
Plugins []string `help:"Plugin paths"`
37-
Router struct {
37+
Plugins []string `help:"Plugin paths" env:"PLUGIN_PATH"`
38+
}
39+
40+
type ServiceConfigCommand struct {
41+
ServiceRunCommand
42+
}
43+
44+
///////////////////////////////////////////////////////////////////////////////
45+
// PUBLIC METHODS
46+
47+
func (cmd *ServiceConfigCommand) Run(app server.Cmd) error {
48+
// Create a provider by loading the plugins
49+
provider, err := provider.NewWithPlugins(cmd.Plugins...)
50+
if err != nil {
51+
return err
52+
}
53+
return provider.WriteConfig(os.Stdout)
54+
}
55+
56+
func (cmd *ServiceRunCommand) Run(app server.Cmd) error {
57+
// Create a provider by loading the plugins
58+
provider, err := provider.NewWithPlugins(cmd.Plugins...)
59+
if err != nil {
60+
return err
61+
}
62+
63+
// Set the configuration
64+
err = errors.Join(err, provider.Load("log", "main", func(ctx context.Context, label string, config server.Plugin) error {
65+
logger := config.(*logger.Config)
66+
logger.Debug = app.GetDebug() >= server.Debug
67+
return nil
68+
}))
69+
err = errors.Join(err, provider.Load("httprouter", "main", func(ctx context.Context, label string, config server.Plugin) error {
70+
httprouter := config.(*httprouter.Config)
71+
httprouter.Prefix = types.NormalisePath(app.GetEndpoint().Path)
72+
httprouter.Origin = "*"
73+
httprouter.Middleware = []string{"log.main"}
74+
return nil
75+
}))
76+
err = errors.Join(err, provider.Load("httpserver", "main", func(ctx context.Context, label string, config server.Plugin) error {
77+
httpserver := config.(*httpserver.Config)
78+
httpserver.Listen = app.GetEndpoint()
79+
80+
// Set router
81+
if router, ok := provider.Task(ctx, "httprouter.main").(http.Handler); !ok || router == nil {
82+
return httpresponse.ErrInternalError.With("Invalid router")
83+
} else {
84+
httpserver.Router = router
85+
}
86+
87+
// Return success
88+
return nil
89+
}))
90+
err = errors.Join(err, provider.Load("helloworld", "main", func(ctx context.Context, label string, config server.Plugin) error {
91+
helloworld := config.(*helloworld.Config)
92+
93+
// Set router
94+
if router, ok := provider.Task(ctx, "httprouter.main").(server.HTTPRouter); !ok || router == nil {
95+
return httpresponse.ErrInternalError.With("Invalid router")
96+
} else {
97+
helloworld.Router = router
98+
}
99+
100+
// Return success
101+
return nil
102+
}))
103+
err = errors.Join(err, provider.Load("pgpool", "main", func(ctx context.Context, label string, config server.Plugin) error {
104+
pgpool := config.(*pg.Config)
105+
106+
// Set router
107+
if router, ok := provider.Task(ctx, "httprouter.main").(server.HTTPRouter); !ok || router == nil {
108+
return httpresponse.ErrInternalError.With("Invalid router")
109+
} else {
110+
pgpool.Router = router
111+
}
112+
113+
// Set trace
114+
if app.GetDebug() == server.Trace {
115+
pgpool.Trace = func(ctx context.Context, query string, args any, err error) {
116+
if err != nil {
117+
ref.Log(ctx).With("args", args).Print(ctx, err, " ON ", query)
118+
} else {
119+
ref.Log(ctx).With("args", args).Debug(ctx, query)
120+
}
121+
}
122+
}
123+
124+
// Return success
125+
return nil
126+
}))
127+
err = errors.Join(err, provider.Load("auth", "main", func(ctx context.Context, label string, config server.Plugin) error {
128+
auth := config.(*auth.Config)
129+
130+
// Set the router
131+
if router, ok := ref.Provider(ctx).Task(ctx, "httprouter").(server.HTTPRouter); !ok || router == nil {
132+
return httpresponse.ErrInternalError.With("Invalid router")
133+
} else {
134+
auth.Router = router
135+
}
136+
137+
// Set the connection pool
138+
if pool, ok := ref.Provider(ctx).Task(ctx, "pgpool").(server.PG); !ok || pool == nil {
139+
return httpresponse.ErrInternalError.With("Invalid connection pool")
140+
} else {
141+
auth.Pool = pool
142+
}
143+
144+
// Return success
145+
return nil
146+
}))
147+
err = errors.Join(err, provider.Load("pgqueue", "main", func(ctx context.Context, label string, config server.Plugin) error {
148+
pgqueue := config.(*pgqueue.Config)
149+
150+
// Set the router
151+
if router, ok := ref.Provider(ctx).Task(ctx, "httprouter").(server.HTTPRouter); !ok || router == nil {
152+
return httpresponse.ErrInternalError.With("Invalid router")
153+
} else {
154+
pgqueue.Router = router
155+
}
156+
157+
// Set the connection pool
158+
if pool, ok := ref.Provider(ctx).Task(ctx, "pgpool").(server.PG); !ok || pool == nil {
159+
return httpresponse.ErrInternalError.With("Invalid connection pool")
160+
} else {
161+
pgqueue.Pool = pool
162+
}
163+
164+
return nil
165+
}))
166+
err = errors.Join(err, provider.Load("certmanager", "main", func(ctx context.Context, label string, config server.Plugin) error {
167+
certmanager := config.(*cert.Config)
168+
169+
// Set the router
170+
if router, ok := ref.Provider(ctx).Task(ctx, "httprouter").(server.HTTPRouter); !ok || router == nil {
171+
return httpresponse.ErrInternalError.With("Invalid router")
172+
} else {
173+
certmanager.Router = router
174+
}
175+
176+
// Set the connection pool
177+
if pool, ok := ref.Provider(ctx).Task(ctx, "pgpool").(server.PG); !ok || pool == nil {
178+
return httpresponse.ErrInternalError.With("Invalid connection pool")
179+
} else {
180+
certmanager.Pool = pool
181+
}
182+
183+
// Set the queue
184+
if queue, ok := ref.Provider(ctx).Task(ctx, "pgqueue").(server.PGQueue); !ok || queue == nil {
185+
return httpresponse.ErrInternalError.With("Invalid task queue")
186+
} else {
187+
certmanager.Queue = queue
188+
}
189+
190+
return nil
191+
}))
192+
if err != nil {
193+
return err
194+
}
195+
196+
// Run the provider
197+
return provider.Run(app.Context())
198+
}
199+
200+
/*
201+
type ServiceRunCommand struct {
202+
Router struct {
38203
httprouter.Config `embed:"" prefix:"router."` // Router configuration
39-
} `embed:""`
204+
} `embed:"" prefix:""`
40205
Server struct {
41206
httpserver.Config `embed:"" prefix:"server."` // Server configuration
42207
} `embed:""`
@@ -55,24 +220,11 @@ type ServiceRunCommand struct {
55220
Log struct {
56221
logger.Config `embed:"" prefix:"log."` // Logger configuration
57222
} `embed:""`
58-
HelloWorld struct {
59-
helloworld.Config `embed:"" prefix:"helloworld."` // HelloWorld configuration
60-
} `embed:""`
61223
}
224+
*/
62225

63-
///////////////////////////////////////////////////////////////////////////////
64-
// PUBLIC METHODS
65-
226+
/*
66227
func (cmd *ServiceRunCommand) Run(app server.Cmd) error {
67-
// Load plugins
68-
plugins, err := provider.LoadPluginsForPattern(cmd.Plugins...)
69-
if err != nil {
70-
return err
71-
}
72-
for _, plugin := range plugins {
73-
fmt.Println("TODO: Loaded plugins:", plugin.Name())
74-
}
75-
76228
// Set the server listener and router prefix
77229
cmd.Server.Listen = app.GetEndpoint()
78230
cmd.Router.Prefix = types.NormalisePath(cmd.Server.Listen.Path)
@@ -134,19 +286,6 @@ func (cmd *ServiceRunCommand) Run(app server.Cmd) error {
134286
// Return the new configuration with the router
135287
return config, nil
136288
137-
case "helloworld":
138-
config := plugin.(helloworld.Config)
139-
140-
// Set the router
141-
if router, ok := ref.Provider(ctx).Task(ctx, "httprouter").(server.HTTPRouter); !ok || router == nil {
142-
return nil, httpresponse.ErrInternalError.Withf("Invalid router %q", "httprouter")
143-
} else {
144-
config.Router = router
145-
}
146-
147-
// Return the new configuration with the router
148-
return config, nil
149-
150289
case "auth":
151290
config := plugin.(auth.Config)
152291
@@ -214,11 +353,12 @@ func (cmd *ServiceRunCommand) Run(app server.Cmd) error {
214353
215354
// No-op
216355
return plugin, nil
217-
}, cmd.Log.Config, cmd.Router.Config, cmd.Server.Config, cmd.HelloWorld.Config, cmd.Auth.Config, cmd.PGPool.Config, cmd.PGQueue.Config, cmd.CertManager.Config)
356+
}, cmd.Log.Config, cmd.Router.Config, cmd.Server.Config, cmd.Auth.Config, cmd.PGPool.Config, cmd.PGQueue.Config, cmd.CertManager.Config)
218357
if err != nil {
219358
return err
220359
}
221360
222361
// Run the provider
223362
return provider.Run(app.Context())
224363
}
364+
*/

etc/docker/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ RUN apt update -y && apt install -y ca-certificates
2525
LABEL org.opencontainers.image.source=https://${SOURCE}
2626

2727
# Entrypoint when running the server
28+
ENV \
29+
PLUGIN_PATH="/usr/local/bin/*.plugin" \
30+
ENDPOINT="http://0.0.0.0:80/"
31+
EXPOSE 80 443
2832
ENTRYPOINT [ "/usr/local/bin/server" ]

0 commit comments

Comments
 (0)