Skip to content

Commit

Permalink
Add env var support in config
Browse files Browse the repository at this point in the history
  • Loading branch information
yarlson committed Oct 16, 2023
1 parent 0f7b7d9 commit d8824d2
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 8 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ GateH8 is a flexible and customizable API Gateway designed to proxy requests to
- [Configuration Guide](#configuration-guide)
- [General Settings](#general-settings)
- [Path Variables and Wildcards](#path-variables-and-wildcards)
- [Environment Variables](#environment-variables)
- [Virtual Hosts and Routes](#virtual-hosts-and-routes)
- [Wildcard Domain Routing](#wildcard-domain-routing)
- [CORS Settings](#cors-settings)
Expand Down Expand Up @@ -99,6 +100,10 @@ Additionally, GateH8 supports wildcards within path configurations. By using an
The above configuration will match and route requests like `/products/1`, `/products/soap`, and so on.
### Environment Variables
GateH8 supports environment variables in the configuration file. This allows you to define dynamic values for your configuration, such as backend URLs, without having to hardcode them.
### Virtual Hosts and Routes
Virtual hosts enable you to route traffic differently based on the domain of the incoming request.
Expand Down
21 changes: 17 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,19 @@ type Config struct {
// GetConfig reads the API Gateway's configuration from a JSON file and returns it.
// It handles any issues with reading or parsing the configuration file.
func GetConfig() (*Config, error) {
content, err := os.ReadFile("config.json")
rawConfig, err := os.ReadFile("config.json")
if err != nil {
return nil, fmt.Errorf("error reading config.json: %w", err)
}

rawConfig = replaceEnvVars(rawConfig)

config := new(Config) // Use new() to get a pointer directly, prevent local-to-heap migration.
if err = json.Unmarshal(content, config); err != nil {
if err = json.Unmarshal(rawConfig, config); err != nil {
return nil, fmt.Errorf("error parsing config.json: %w", err)
}

anyVhostWithSSL, allVhostsWithSSL := check(config)
anyVhostWithSSL, allVhostsWithSSL := checkVhostsWithTLS(config)

// If there's any vhost with TLS configured but not all of them have, then it's a config error.
if anyVhostWithSSL && !allVhostsWithSSL {
Expand All @@ -103,7 +105,18 @@ func GetConfig() (*Config, error) {
return config, nil
}

func check(config *Config) (bool, bool) {
func replaceEnvVars(rawConfig []byte) []byte {
// replace ${path} with [[path]] to avoid env var replacement
rawConfig = []byte(strings.ReplaceAll(string(rawConfig), "${path}", "[[path]]"))
// replace env vars
rawConfig = []byte(os.ExpandEnv(string(rawConfig)))
// replace [[path]] with ${path} to restore original value
rawConfig = []byte(strings.ReplaceAll(string(rawConfig), "[[path]]", "${path}"))

return rawConfig
}

func checkVhostsWithTLS(config *Config) (bool, bool) {
// Check if any vhost has TLS configured
anyVhostWithSSL := false
allVhostsWithSSL := true
Expand Down
199 changes: 199 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package config

import (
"os"
"reflect"
"testing"
)

func Test_replaceEnvVars(t *testing.T) {
type args struct {
rawConfig []byte
}
tests := []struct {
name string
args args
envName string
envValue string
want []byte
}{
{
name: "test",
args: args{
rawConfig: []byte(`{
{
"apiGateway": {
"name": "MyAPIGateway",
"version": "1.0.0"
},
"vhosts": {
"*": {
"endpoints": [
{
"path": "/ws",
"backend": {
"url": "ws://127.0.0.1:8080/ws/$TEST"
},
"websocket": {
"readBufferSize": 1024,
"writeBufferSize": 1024,
"allowedOrigins": ["*"]
}
}
]
}
}
}
}`),
},
envName: "TEST",
envValue: "test",
want: []byte(`{
{
"apiGateway": {
"name": "MyAPIGateway",
"version": "1.0.0"
},
"vhosts": {
"*": {
"endpoints": [
{
"path": "/ws",
"backend": {
"url": "ws://127.0.0.1:8080/ws/test"
},
"websocket": {
"readBufferSize": 1024,
"writeBufferSize": 1024,
"allowedOrigins": ["*"]
}
}
]
}
}
}
}`),
},
{
name: "path",
args: args{
rawConfig: []byte(`{
{
"apiGateway": {
"name": "MyAPIGateway",
"version": "1.0.0"
},
"vhosts": {
"*": {
"endpoints": [
{
"path": "/ws",
"backend": {
"url": "ws://127.0.0.1:8080/ws"
},
"websocket": {
"readBufferSize": 1024,
"writeBufferSize": 1024,
"allowedOrigins": ["*"]
}
}
]
}
}
}
}`),
},
want: []byte(`{
{
"apiGateway": {
"name": "MyAPIGateway",
"version": "1.0.0"
},
"vhosts": {
"*": {
"endpoints": [
{
"path": "/ws",
"backend": {
"url": "ws://127.0.0.1:8080/ws"
},
"websocket": {
"readBufferSize": 1024,
"writeBufferSize": 1024,
"allowedOrigins": ["*"]
}
}
]
}
}
}
}`),
envName: "path",
envValue: "test",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = os.Setenv(tt.envName, tt.envValue)
if got := replaceEnvVars(tt.args.rawConfig); !reflect.DeepEqual(got, tt.want) {
t.Errorf("replaceEnvVars() = %v, wantAny %v", got, tt.want)
}
})
}
}

func Test_checkVhostsWithTLS(t *testing.T) {
type args struct {
config *Config
}
tests := []struct {
name string
args args
wantAny bool
wantAll bool
}{
{
name: "test",
args: args{
config: &Config{Vhosts: map[string]Vhost{
"www.sample.org": {TLS: &TLSConfig{}},
}},
},
wantAny: true,
wantAll: true,
},
{
name: "test",
args: args{
config: &Config{Vhosts: map[string]Vhost{
"www.sample.org": {TLS: &TLSConfig{}},
"www1.sample.org": {},
}},
},
wantAny: true,
wantAll: false,
},
{
name: "test",
args: args{
config: &Config{Vhosts: map[string]Vhost{
"www.sample.org": {},
"www1.sample.org": {},
}},
},
wantAny: false,
wantAll: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1 := checkVhostsWithTLS(tt.args.config)
if got != tt.wantAny {
t.Errorf("checkVhostsWithTLS() got = %v, wantAny %v", got, tt.wantAny)
}
if got1 != tt.wantAll {
t.Errorf("checkVhostsWithTLS() got1 = %v, wantAny %v", got1, tt.wantAll)
}
})
}
}
4 changes: 2 additions & 2 deletions example/http/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"GET"
],
"backend": {
"url": "http://nginx1/",
"url": "http://${HOST1}/",
"timeout": 5000
}
},
Expand All @@ -22,7 +22,7 @@
"GET"
],
"backend": {
"url": "http://nginx2/",
"url": "http://${HOST2}/",
"timeout": 5000
}
}
Expand Down
5 changes: 4 additions & 1 deletion example/http/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
services:
gateh8:
image: yarlson/gateh8:0.2.1
image: yarlson/gateh8:0.3.0
ports:
- "80:80"
environment:
- HOST1=nginx1
- HOST2=nginx2
volumes:
- ./config.json:/config.json
command: ["-a", ":80"]
Expand Down
2 changes: 1 addition & 1 deletion example/websockets/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
gateh8:
image: yarlson/gateh8:0.2.1
image: yarlson/gateh8:0.3.0
ports:
- "80:80"
volumes:
Expand Down

0 comments on commit d8824d2

Please sign in to comment.