diff --git a/cmd/config/check.go b/cmd/config/check.go index 2a563c5..ce6c762 100644 --- a/cmd/config/check.go +++ b/cmd/config/check.go @@ -18,9 +18,10 @@ package config import ( + "fmt" pkg "github.com/jkaninda/goma-gateway/internal" "github.com/spf13/cobra" - "log" + "os" ) var CheckConfigCmd = &cobra.Command{ @@ -29,13 +30,15 @@ var CheckConfigCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { configFile, _ := cmd.Flags().GetString("config") if configFile == "" { - log.Fatalln("no config file specified") + fmt.Println("no config file specified") + os.Exit(1) } err := pkg.CheckConfig(configFile) if err != nil { - log.Fatalf(" Error checking config file: %s\n", err) + fmt.Printf(" Error checking config file: %s\n", err) + os.Exit(1) } - log.Println("Goma Gateway configuration file checked successfully") + fmt.Println("Goma Gateway configuration file checked successfully") }, } diff --git a/cmd/config/config.go b/cmd/config/config.go index 302ec75..7ea675f 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -17,8 +17,9 @@ limitations under the License. package config import ( + "fmt" "github.com/spf13/cobra" - "log" + "os" ) var Cmd = &cobra.Command{ @@ -28,8 +29,8 @@ var Cmd = &cobra.Command{ if len(args) == 0 { return } else { - log.Fatalf("Config accepts no argument %q", args) - + fmt.Printf("config accepts no argument %q\n", args) + os.Exit(1) } }, diff --git a/cmd/config/init.go b/cmd/config/init.go index b48f66b..789b211 100644 --- a/cmd/config/init.go +++ b/cmd/config/init.go @@ -16,9 +16,10 @@ See the License for the specific language governing permissions and limitations under the License. */ import ( + "fmt" "github.com/jkaninda/goma-gateway/internal" - "github.com/jkaninda/goma-gateway/pkg/logger" "github.com/spf13/cobra" + "os" ) var InitConfigCmd = &cobra.Command{ @@ -26,14 +27,34 @@ var InitConfigCmd = &cobra.Command{ Short: "Initialize Goma Gateway configuration file", Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { - pkg.InitConfig(cmd) + force, _ := cmd.Flags().GetBool("force") + configFile, _ := cmd.Flags().GetString("output") + if configFile == "" { + fmt.Println("Error: no config file specified") + os.Exit(1) + } + // Check if the config file exists + if _, err := os.Stat(configFile); !os.IsNotExist(err) { + if !force { + fmt.Printf("%s config file already exists, use -f to overwrite\n", configFile) + os.Exit(1) + } + } + err := pkg.InitConfig(configFile) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("configuration file has been initialized successfully") } else { - logger.Fatal(`"config" accepts no argument %q`, args) + fmt.Printf("config accepts no argument %q\n", args) + os.Exit(1) } }, } func init() { - InitConfigCmd.Flags().StringP("output", "o", "", "config file output") + InitConfigCmd.Flags().StringP("output", "o", "", "configuration file output") + InitConfigCmd.Flags().BoolP("force", "f", false, "Force overwrite configuration file") } diff --git a/cmd/root.go b/cmd/root.go index bbe5bf3..22e9bf1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,10 +17,11 @@ limitations under the License. package cmd import ( + "fmt" "github.com/jkaninda/goma-gateway/cmd/config" - "github.com/jkaninda/goma-gateway/pkg/logger" "github.com/jkaninda/goma-gateway/util" "github.com/spf13/cobra" + "os" ) // rootCmd represents @@ -29,7 +30,7 @@ var rootCmd = &cobra.Command{ Short: "Goma Gateway is a lightweight API Gateway Management", Long: `.`, Example: util.MainExample, - Version: util.FullVersion(), + Version: util.Version, } // Execute adds all child commands to the root command and sets flags appropriately. @@ -37,7 +38,8 @@ var rootCmd = &cobra.Command{ func Execute() { err := rootCmd.Execute() if err != nil { - logger.Fatal("Error executing root command %v", err) + fmt.Printf("Error executing root command %v\n", err) + os.Exit(1) } } func init() { diff --git a/cmd/server.go b/cmd/server.go index 0fa3531..ec2c36b 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -21,9 +21,9 @@ import ( "fmt" "github.com/common-nighthawk/go-figure" "github.com/jkaninda/goma-gateway/internal" - "github.com/jkaninda/goma-gateway/pkg/logger" "github.com/jkaninda/goma-gateway/util" "github.com/spf13/cobra" + "os" ) var ServerCmd = &cobra.Command{ @@ -39,11 +39,13 @@ var ServerCmd = &cobra.Command{ g := pkg.GatewayServer{} gs, err := g.Config(configFile) if err != nil { - logger.Fatal("Could not load configuration: %v", err) + fmt.Printf("Could not load configuration: %v\n", err) + os.Exit(1) } gs.SetEnv() if err := gs.Start(ctx); err != nil { - logger.Fatal("Could not start server: %v", err) + fmt.Printf("Could not start server: %v\n", err) + os.Exit(1) } @@ -56,7 +58,7 @@ func init() { func intro() { nameFigure := figure.NewFigure("Goma", "", true) nameFigure.Print() - fmt.Printf("Version: %s\n", util.FullVersion()) + fmt.Printf("Version: %s\n", util.Version) fmt.Println("Copyright (c) 2024 Jonas Kaninda") fmt.Println("Starting Goma Gateway server...") } diff --git a/cmd/version.go b/cmd/version.go index 99a1f96..72cec1c 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -18,7 +18,6 @@ package cmd import ( - "fmt" "github.com/jkaninda/goma-gateway/util" "github.com/spf13/cobra" ) @@ -32,5 +31,5 @@ var VersionCmd = &cobra.Command{ } func version() { - fmt.Println("Version:\t", util.FullVersion()) + util.FullVersion() } diff --git a/internal/checkConfig.go b/internal/checkConfig.go index 67c568e..4c99e28 100644 --- a/internal/checkConfig.go +++ b/internal/checkConfig.go @@ -21,7 +21,6 @@ import ( "fmt" "github.com/jkaninda/goma-gateway/util" "gopkg.in/yaml.v3" - "log" "os" ) @@ -46,21 +45,21 @@ func CheckConfig(fileName string) error { } for index, route := range gateway.gateway.Routes { if len(route.Name) == 0 { - log.Printf("Warning: route name is empty, index: [%d]", index) + fmt.Printf("Warning: route name is empty, index: [%d]", index) } if route.Destination == "" && len(route.Backends) == 0 { - log.Printf("Error: no destination or backends specified for route: %s | index: [%d] \n", route.Name, index) + fmt.Printf("Error: no destination or backends specified for route: %s | index: [%d] \n", route.Name, index) } } //Check middleware for index, mid := range c.Middlewares { if util.HasWhitespace(mid.Name) { - log.Printf("Warning: Middleware contains whitespace: %s | index: [%d], please remove whitespace characters\n", mid.Name, index) + fmt.Printf("Warning: Middleware contains whitespace: %s | index: [%d], please remove whitespace characters\n", mid.Name, index) } } - log.Printf("Routes count=%d Middlewares count=%d\n", len(gateway.gateway.Routes), len(gateway.middlewares)) + fmt.Printf("Routes count=%d Middlewares count=%d\n", len(gateway.gateway.Routes), len(gateway.middlewares)) return nil diff --git a/internal/config.go b/internal/config.go index 9dc1f7f..929e7c0 100644 --- a/internal/config.go +++ b/internal/config.go @@ -20,7 +20,6 @@ import ( "github.com/jkaninda/goma-gateway/internal/middleware" "github.com/jkaninda/goma-gateway/pkg/logger" "github.com/jkaninda/goma-gateway/util" - "github.com/spf13/cobra" "golang.org/x/oauth2" "golang.org/x/oauth2/amazon" "golang.org/x/oauth2/facebook" @@ -83,7 +82,11 @@ func (GatewayServer) Config(configFile string) (*GatewayServer, error) { return nil, err } } - initConfig(ConfigFile) + err := initConfig(ConfigFile) + if err != nil { + return nil, err + } + logger.Info("Generating new configuration file...done") logger.Info("Server configuration file is available at %s", ConfigFile) util.SetEnv("GOMA_CONFIG_FILE", ConfigFile) buf, err := os.ReadFile(ConfigFile) @@ -115,18 +118,13 @@ func GetConfigPaths() string { } // InitConfig initializes configs -func InitConfig(cmd *cobra.Command) { - configFile, _ := cmd.Flags().GetString("output") - if configFile == "" { - configFile = GetConfigPaths() - } - initConfig(configFile) - return +func InitConfig(configFile string) error { + return initConfig(configFile) } // initConfig initializes configs -func initConfig(configFile string) { +func initConfig(configFile string) error { if configFile == "" { configFile = GetConfigPaths() } @@ -292,13 +290,13 @@ func initConfig(configFile string) { } yamlData, err := yaml.Marshal(&conf) if err != nil { - logger.Fatal("Error serializing configuration %v", err.Error()) + return fmt.Errorf("serializing configuration %v\n", err.Error()) } err = os.WriteFile(configFile, yamlData, 0644) if err != nil { - logger.Fatal("Unable to write config file %s", err) + return fmt.Errorf("unable to write config file %s\n", err) } - logger.Info("Configuration file has been initialized successfully") + return nil } func (Gateway) Setup(conf string) *Gateway { if util.FileExists(conf) { diff --git a/internal/helpers.go b/internal/helpers.go index eb224f6..2cefa70 100644 --- a/internal/helpers.go +++ b/internal/helpers.go @@ -27,7 +27,12 @@ func printRoute(routes []Route) { t := table.NewWriter() t.AppendHeader(table.Row{"Name", "Route", "Rewrite", "Destination"}) for _, route := range routes { - t.AppendRow(table.Row{route.Name, route.Path, route.Rewrite, route.Destination}) + if len(route.Backends) > 0 { + t.AppendRow(table.Row{route.Name, route.Path, route.Rewrite, fmt.Sprintf("backends: [%d]", len(route.Backends))}) + + } else { + t.AppendRow(table.Row{route.Name, route.Path, route.Rewrite, route.Destination}) + } } fmt.Println(t.Render()) } diff --git a/internal/prometheus.go b/internal/prometheus.go index a1c0273..9bc62c5 100644 --- a/internal/prometheus.go +++ b/internal/prometheus.go @@ -35,7 +35,7 @@ var totalRequests = prometheus.NewCounterVec( Name: "http_requests_total", Help: "Number of get requests.", }, - []string{"path"}, + []string{"name", "path"}, ) var responseStatus = prometheus.NewCounterVec( @@ -49,16 +49,19 @@ var responseStatus = prometheus.NewCounterVec( var httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "http_response_time_seconds", Help: "Duration of HTTP requests.", -}, []string{"path"}) +}, []string{"name", "path"}) -func prometheusMiddleware(next http.Handler) http.Handler { +func (pr PrometheusRoute) prometheusMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - route := mux.CurrentRoute(r) - path, _ := route.GetPathTemplate() - timer := prometheus.NewTimer(httpDuration.WithLabelValues(path)) + path := pr.path + if len(path) == 0 { + route := mux.CurrentRoute(r) + path, _ = route.GetPathTemplate() + } + timer := prometheus.NewTimer(httpDuration.WithLabelValues(pr.name, path)) responseStatus.WithLabelValues(strconv.Itoa(http.StatusOK)).Inc() - totalRequests.WithLabelValues(path).Inc() + totalRequests.WithLabelValues(pr.name, path).Inc() timer.ObserveDuration() next.ServeHTTP(w, r) diff --git a/internal/route.go b/internal/route.go index 83b0987..34f1dea 100644 --- a/internal/route.go +++ b/internal/route.go @@ -43,9 +43,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { if gateway.EnableMetrics { // Prometheus endpoint r.Path("/metrics").Handler(promhttp.Handler()) - r.Use(prometheusMiddleware) } - // Routes health check if !gateway.DisableHealthCheckStatus { r.HandleFunc("/healthz", heath.HealthCheckHandler).Methods("GET") @@ -238,7 +236,14 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { } else { router.PathPrefix("").Handler(proxyRoute.ProxyHandler()) } - + if gateway.EnableMetrics { + pr := PrometheusRoute{ + name: route.Name, + path: route.Path, + } + // Prometheus endpoint + router.Use(pr.prometheusMiddleware) + } } else { logger.Error("Error, path is empty in route %s", route.Name) logger.Error("Route path ignored: %s", route.Path) diff --git a/util/constants.go b/util/constants.go index 6757d9b..37a1e99 100644 --- a/util/constants.go +++ b/util/constants.go @@ -10,26 +10,20 @@ You may get a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 */ import ( - "os" + "fmt" ) -var Version string - const ConfigVersion = "1.0" -func VERSION(def string) string { - build := os.Getenv("VERSION") - if build == "" { - return def - } - return build -} -func FullVersion() string { - ver := Version - if b := VERSION(""); b != "" { - return b - } - return ver +var Version = "development" +var buildTime string +var gitCommit string + +func FullVersion() { + fmt.Printf("Goma Gateway version: %s\n", Version) + fmt.Printf("Configuration version: %s\n", ConfigVersion) + fmt.Printf("Build time: %s\n", buildTime) + fmt.Printf("Git commit: %s\n", gitCommit) } const MainExample = "Initialize config: config init --output config.yml\n" +