diff --git a/internal/config.go b/internal/config.go index 6fdcbe3..7b2f569 100644 --- a/internal/config.go +++ b/internal/config.go @@ -76,7 +76,7 @@ func (GatewayServer) Config(configFile string, ctx context.Context) (*GatewaySer } logger.Info("Generating new configuration file...") - // check if config directory does exist + // check if config Directory does exist if !util.FolderExists(ConfigDir) { err := os.MkdirAll(ConfigDir, os.ModePerm) if err != nil { diff --git a/internal/extra_config.go b/internal/extra_config.go new file mode 100644 index 0000000..60d72f0 --- /dev/null +++ b/internal/extra_config.go @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Jonas Kaninda + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package pkg + +import ( + "fmt" + "os" + "path/filepath" +) + +// loadExtraFiles loads routes files in .yml and .yaml based on defined directory +func loadExtraFiles(routePath string) ([]string, error) { + // Slice to store YAML/YML files + var yamlFiles []string + // Walk through the Directory + err := filepath.Walk(routePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + // Check for .yaml or .yml file extension + if !info.IsDir() && (filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml") { + yamlFiles = append(yamlFiles, path) + } + return nil + }) + + if err != nil { + //log.Fatalf("error walking the path %v: %v", routePath, err) + return nil, fmt.Errorf("error loading extra route files: %v", err) + } + return yamlFiles, nil +} diff --git a/internal/gateway_type.go b/internal/gateway_type.go index 7118f4e..f3e55a3 100644 --- a/internal/gateway_type.go +++ b/internal/gateway_type.go @@ -34,10 +34,11 @@ type Gateway struct { // RateLimit Defines the number of request peer minutes RateLimit int `yaml:"rateLimit" env:"GOMA_RATE_LIMIT, overwrite"` // BlockCommonExploits enable, disable block common exploits - BlockCommonExploits bool `yaml:"blockCommonExploits"` - AccessLog string `yaml:"accessLog" env:"GOMA_ACCESS_LOG, overwrite"` - ErrorLog string `yaml:"errorLog" env:"GOMA_ERROR_LOG=, overwrite"` - LogLevel string `yaml:"logLevel" env:"GOMA_LOG_LEVEL, overwrite"` + BlockCommonExploits bool `yaml:"blockCommonExploits"` + AccessLog string `yaml:"accessLog" env:"GOMA_ACCESS_LOG, overwrite"` + ErrorLog string `yaml:"errorLog" env:"GOMA_ERROR_LOG=, overwrite"` + LogLevel string `yaml:"logLevel" env:"GOMA_LOG_LEVEL, overwrite"` + ExtraRoutes ExtraRouteConfig `yaml:"extraRoutes"` // DisableHealthCheckStatus enable and disable routes health check DisableHealthCheckStatus bool `yaml:"disableHealthCheckStatus"` // DisableRouteHealthCheckError allows enabling and disabling backend healthcheck errors diff --git a/internal/route.go b/internal/route.go index 4bb75bf..137dccc 100644 --- a/internal/route.go +++ b/internal/route.go @@ -35,18 +35,27 @@ func init() { // Initialize initializes the routes func (gatewayServer GatewayServer) Initialize() *mux.Router { gateway := gatewayServer.gateway + dynamicRoutes = gateway.Routes + // Load Extra Routes + if len(gateway.ExtraRoutes.Directory) != 0 { + extraRoutes, err := loadExtraRoutes(gateway.ExtraRoutes.Directory) + if err != nil { + logger.Error(err.Error()) + } + dynamicRoutes = append(dynamicRoutes, extraRoutes...) + } m := gatewayServer.middlewares redisBased := false if len(gateway.Redis.Addr) != 0 { redisBased = true } // Routes background healthcheck - routesHealthCheck(gateway.Routes) + routesHealthCheck(dynamicRoutes) r := mux.NewRouter() heath := HealthCheckRoute{ DisableRouteHealthCheckError: gateway.DisableRouteHealthCheckError, - Routes: gateway.Routes, + Routes: dynamicRoutes, } if gateway.EnableMetrics { // Prometheus endpoint @@ -80,7 +89,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { // Add rate limit middlewares r.Use(limiter.RateLimitMiddleware()) } - for rIndex, route := range gateway.Routes { + for rIndex, route := range dynamicRoutes { if len(route.Path) != 0 { // Checks if route destination and backend are empty if len(route.Destination) == 0 && len(route.Backends) == 0 { diff --git a/internal/route_config.go b/internal/route_config.go new file mode 100644 index 0000000..07e337c --- /dev/null +++ b/internal/route_config.go @@ -0,0 +1,55 @@ +/* + * Copyright 2024 Jonas Kaninda + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package pkg + +import ( + "fmt" + "github.com/jkaninda/goma-gateway/pkg/logger" + "gopkg.in/yaml.v3" + "os" +) + +// loadExtraRoutes loads additional routes +func loadExtraRoutes(routePath string) ([]Route, error) { + logger.Info("Loading additional routes from %s", routePath) + yamlFiles, err := loadExtraFiles(routePath) + if err != nil { + return nil, fmt.Errorf("error loading extra files: %v", err) + } + var extraRoutes []Route + for _, yamlFile := range yamlFiles { + buf, err := os.ReadFile(yamlFile) + if err != nil { + return nil, fmt.Errorf("error loading extra file: %v", err) + } + ex := &ExtraRoute{} + err = yaml.Unmarshal(buf, ex) + if err != nil { + return nil, fmt.Errorf("in file %q: %w", ConfigFile, err) + } + extraRoutes = append(extraRoutes, ex.Routes...) + + } + if len(extraRoutes) == 0 { + return nil, fmt.Errorf("no extra routes found in %s", routePath) + } else { + logger.Info("Loaded %d extra routes from %s", len(extraRoutes), routePath) + + } + return extraRoutes, nil +} diff --git a/internal/route_type.go b/internal/route_type.go index b09e894..b544eee 100644 --- a/internal/route_type.go +++ b/internal/route_type.go @@ -56,3 +56,8 @@ type Route struct { // Middlewares Defines route middlewares from Middleware names Middlewares []string `yaml:"middlewares"` } + +type ExtraRoute struct { + // Routes holds proxy routes + Routes []Route `yaml:"routes"` +} diff --git a/internal/server.go b/internal/server.go index 947f1ec..340b177 100644 --- a/internal/server.go +++ b/internal/server.go @@ -41,7 +41,7 @@ func (gatewayServer GatewayServer) Start() error { } if !gatewayServer.gateway.DisableDisplayRouteOnStart { - printRoute(gatewayServer.gateway.Routes) + printRoute(dynamicRoutes) } httpServer := gatewayServer.createServer(":8080", route, nil) diff --git a/internal/types.go b/internal/types.go index ca0c724..8f05891 100644 --- a/internal/types.go +++ b/internal/types.go @@ -168,3 +168,8 @@ type Redis struct { Addr string `yaml:"addr"` Password string `yaml:"password"` } + +type ExtraRouteConfig struct { + Directory string `yaml:"directory"` + Watch bool `yaml:"watch"` +} diff --git a/internal/var.go b/internal/var.go index c83716a..4a71ff5 100644 --- a/internal/var.go +++ b/internal/var.go @@ -8,7 +8,9 @@ const AccessMiddleware = "access" // access middlewares const BasicAuth = "basic" // basic authentication middlewares const JWTAuth = "jwt" // JWT authentication middlewares const OAuth = "oauth" // OAuth authentication middlewares -// Round-robin counter -var counter uint32 - -var Routes *[]Route +var ( + // Round-robin counter + counter uint32 + // dynamicRoutes routes + dynamicRoutes []Route +)