Skip to content

Commit d7f57e1

Browse files
vertex451akafazov
andauthored
Feat(gateway): viper config and sentry (#192)
* feat: moved config to viper * feat: sentry --------- Signed-off-by: Artem Shcherbatiuk <[email protected]> Signed-off-by: Angel Kafazov <[email protected]> Co-authored-by: Angel Kafazov <[email protected]>
1 parent f81f262 commit d7f57e1

File tree

13 files changed

+236
-163
lines changed

13 files changed

+236
-163
lines changed

cmd/gateway.go

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,50 @@
11
package cmd
22

33
import (
4+
"context"
5+
"errors"
46
"fmt"
5-
"net/http"
6-
"time"
7-
8-
"github.com/rs/zerolog/log"
9-
7+
openmfpcontext "github.com/openmfp/golang-commons/context"
8+
"github.com/openmfp/golang-commons/sentry"
109
"github.com/spf13/cobra"
10+
"net/http"
1111
ctrl "sigs.k8s.io/controller-runtime"
1212
restCfg "sigs.k8s.io/controller-runtime/pkg/client/config"
1313
"sigs.k8s.io/controller-runtime/pkg/log/zap"
14+
"time"
1415

1516
"github.com/openmfp/golang-commons/logger"
1617

17-
appConfig "github.com/openmfp/kubernetes-graphql-gateway/common/config"
1818
"github.com/openmfp/kubernetes-graphql-gateway/gateway/manager"
1919
)
2020

2121
var gatewayCmd = &cobra.Command{
2222
Use: "gateway",
2323
Short: "Run the GQL Gateway",
2424
Example: "go run main.go gateway",
25-
RunE: func(cmd *cobra.Command, args []string) error {
26-
start := time.Now()
27-
28-
appCfg, err := appConfig.NewFromEnv()
29-
if err != nil {
30-
log.Fatal().Err(err).Msg("Error getting app restCfg, exiting")
31-
}
32-
33-
log, err := setupLogger(appCfg.LogLevel)
25+
RunE: func(_ *cobra.Command, _ []string) error {
26+
log, err := setupLogger(defaultCfg.Log.Level)
3427
if err != nil {
3528
return fmt.Errorf("failed to setup logger: %w", err)
3629
}
3730

3831
log.Info().Str("LogLevel", log.GetLevel().String()).Msg("Starting server...")
3932

33+
ctx, _, shutdown := openmfpcontext.StartContext(log, appCfg, 1*time.Second)
34+
defer shutdown()
35+
36+
if defaultCfg.Sentry.Dsn != "" {
37+
err := sentry.Start(ctx,
38+
defaultCfg.Sentry.Dsn, defaultCfg.Environment, defaultCfg.Region,
39+
defaultCfg.Image.Name, defaultCfg.Image.Tag,
40+
)
41+
if err != nil {
42+
log.Fatal().Err(err).Msg("Sentry init failed")
43+
}
44+
45+
defer openmfpcontext.Recover(log)
46+
}
47+
4048
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
4149

4250
// Get Kubernetes restCfg
@@ -55,15 +63,34 @@ var gatewayCmd = &cobra.Command{
5563
// Set up HTTP handler
5664
http.Handle("/", managerInstance)
5765

58-
// Start HTTP server
59-
err = http.ListenAndServe(fmt.Sprintf(":%s", appCfg.Port), nil)
60-
if err != nil {
61-
log.Error().Err(err).Msg("Error starting server")
62-
return fmt.Errorf("failed to start server: %w", err)
66+
// Start HTTP server with context
67+
server := &http.Server{
68+
Addr: fmt.Sprintf(":%s", appCfg.Gateway.Port),
69+
Handler: nil,
70+
}
71+
72+
// Start the HTTP server in a goroutine so that we can listen for shutdown signals
73+
go func() {
74+
err := server.ListenAndServe()
75+
if err != nil && !errors.Is(err, http.ErrServerClosed) {
76+
log.Error().Err(err).Msg("Error starting HTTP server")
77+
}
78+
}()
79+
80+
// Wait for shutdown signal via the context
81+
<-ctx.Done()
82+
83+
shutdownCtx, cancel := context.WithTimeout(context.Background(), defaultCfg.ShutdownTimeout) // ctx is closed, we need a new one
84+
defer cancel()
85+
log.Info().Msg("Shutting down HTTP server...")
86+
if err := server.Shutdown(shutdownCtx); err != nil {
87+
log.Fatal().Err(err).Msg("HTTP server shutdown failed")
6388
}
6489

65-
log.Info().Float64("elapsed_seconds", time.Since(start).Seconds()).Msg("Setup completed")
90+
// Call the shutdown cleanup
91+
shutdown()
6692

93+
log.Info().Msg("Server shut down successfully")
6794
return nil
6895
},
6996
}

cmd/listener.go

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,14 @@ import (
44
"crypto/tls"
55
"os"
66

7-
"k8s.io/client-go/discovery"
8-
9-
"github.com/openmfp/kubernetes-graphql-gateway/listener/discoveryclient"
10-
117
kcpapis "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
128
kcpcore "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
139
kcptenancy "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1"
14-
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
15-
1610
"github.com/spf13/cobra"
17-
11+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1812
"k8s.io/apimachinery/pkg/runtime"
1913
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
20-
14+
"k8s.io/client-go/discovery"
2115
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
2216
ctrl "sigs.k8s.io/controller-runtime"
2317
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -27,28 +21,22 @@ import (
2721
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
2822
"sigs.k8s.io/controller-runtime/pkg/webhook"
2923

30-
"github.com/openmfp/kubernetes-graphql-gateway/common/config"
24+
"github.com/openmfp/kubernetes-graphql-gateway/listener/discoveryclient"
3125
"github.com/openmfp/kubernetes-graphql-gateway/listener/kcp"
3226
)
3327

34-
func init() {
35-
rootCmd.AddCommand(listenCmd)
36-
}
37-
3828
var (
3929
scheme = runtime.NewScheme()
4030
setupLog = ctrl.Log.WithName("setup")
4131
webhookServer webhook.Server
4232
metricsServerOptions metricsserver.Options
43-
appCfg config.Config
4433
)
4534

4635
var listenCmd = &cobra.Command{
4736
Use: "listener",
4837
Example: "KUBECONFIG=<path to kubeconfig file> go run . listener",
4938
PreRun: func(cmd *cobra.Command, args []string) {
5039
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
51-
5240
utilruntime.Must(kcpapis.AddToScheme(scheme))
5341
utilruntime.Must(kcpcore.AddToScheme(scheme))
5442
utilruntime.Must(kcptenancy.AddToScheme(scheme))
@@ -59,20 +47,13 @@ var listenCmd = &cobra.Command{
5947
}
6048
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
6149

62-
var err error
63-
appCfg, err = config.NewFromEnv()
64-
if err != nil {
65-
setupLog.Error(err, "failed to get operator flags from env, exiting...")
66-
os.Exit(1)
67-
}
68-
6950
disableHTTP2 := func(c *tls.Config) {
7051
setupLog.Info("disabling http/2")
7152
c.NextProtos = []string{"http/1.1"}
7253
}
7354

7455
var tlsOpts []func(*tls.Config)
75-
if !appCfg.EnableHTTP2 {
56+
if !defaultCfg.EnableHTTP2 {
7657
tlsOpts = []func(c *tls.Config){disableHTTP2}
7758
}
7859

@@ -81,19 +62,19 @@ var listenCmd = &cobra.Command{
8162
})
8263

8364
metricsServerOptions = metricsserver.Options{
84-
BindAddress: appCfg.MetricsAddr,
85-
SecureServing: appCfg.SecureMetrics,
65+
BindAddress: defaultCfg.Metrics.BindAddress,
66+
SecureServing: defaultCfg.Metrics.Secure,
8667
TLSOpts: tlsOpts,
8768
}
8869

89-
if appCfg.SecureMetrics {
70+
if defaultCfg.Metrics.Secure {
9071
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
9172
}
9273
},
9374
Run: func(cmd *cobra.Command, args []string) {
9475
ctx := ctrl.SetupSignalHandler()
9576
restCfg := ctrl.GetConfigOrDie()
96-
log, err := setupLogger(appCfg.LogLevel)
77+
log, err := setupLogger(defaultCfg.Log.Level)
9778
if err != nil {
9879
setupLog.Error(err, "unable to setup logger")
9980
os.Exit(1)
@@ -103,8 +84,8 @@ var listenCmd = &cobra.Command{
10384
Scheme: scheme,
10485
Metrics: metricsServerOptions,
10586
WebhookServer: webhookServer,
106-
HealthProbeBindAddress: appCfg.ProbeAddr,
107-
LeaderElection: appCfg.EnableLeaderElection,
87+
HealthProbeBindAddress: defaultCfg.HealthProbeBindAddress,
88+
LeaderElection: defaultCfg.LeaderElection.Enabled,
10889
LeaderElectionID: "72231e1f.openmfp.io",
10990
}
11091

cmd/root.go

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,64 @@ package cmd
22

33
import (
44
"github.com/spf13/cobra"
5+
"github.com/spf13/viper"
6+
7+
openmfpconfig "github.com/openmfp/golang-commons/config"
8+
"github.com/openmfp/kubernetes-graphql-gateway/common/config"
9+
)
10+
11+
var (
12+
appCfg config.Config
13+
defaultCfg *openmfpconfig.CommonServiceConfig
14+
v *viper.Viper
515
)
616

717
var rootCmd = &cobra.Command{
8-
Use: "gateway",
18+
Use: "listener or gateway",
919
}
1020

1121
func init() {
1222
rootCmd.AddCommand(gatewayCmd)
13-
}
23+
rootCmd.AddCommand(listenCmd)
1424

15-
func Execute() {
16-
if err := rootCmd.Execute(); err != nil {
25+
var err error
26+
v, defaultCfg, err = openmfpconfig.NewDefaultConfig(rootCmd)
27+
if err != nil {
28+
panic(err)
29+
}
30+
31+
cobra.OnInitialize(initConfig)
32+
33+
err = openmfpconfig.BindConfigToFlags(v, gatewayCmd, &appCfg)
34+
if err != nil {
1735
panic(err)
1836
}
1937
}
38+
39+
func initConfig() {
40+
// Top-level defaults
41+
v.SetDefault("openapi-definitions-path", "./bin/definitions")
42+
v.SetDefault("enable-kcp", true)
43+
v.SetDefault("local-development", false)
44+
45+
// Listener
46+
v.SetDefault("listener-apiexport-workspace", ":root")
47+
v.SetDefault("listener-apiexport-name", "kcp.io")
48+
49+
// Gateway
50+
v.SetDefault("gateway-port", "9080")
51+
v.SetDefault("gateway-username-claim", "email")
52+
v.SetDefault("gateway-should-impersonate", true)
53+
// Gateway Handler config
54+
v.SetDefault("gateway-handler-pretty", true)
55+
v.SetDefault("gateway-handler-playground", true)
56+
v.SetDefault("gateway-handler-graphiql", true)
57+
// Gateway CORS
58+
v.SetDefault("gateway-cors-enabled", false)
59+
v.SetDefault("gateway-cors-allowed-origins", []string{"*"})
60+
v.SetDefault("gateway-cors-allowed-headers", []string{"*"})
61+
}
62+
63+
func Execute() {
64+
cobra.CheckErr(rootCmd.Execute())
65+
}

common/config/config.go

Lines changed: 25 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,29 @@
11
package config
22

3-
import (
4-
"github.com/vrischmann/envconfig"
5-
)
6-
73
type Config struct {
8-
Listener
9-
Gateway
10-
11-
OpenApiDefinitionsPath string `envconfig:"default=./bin/definitions"`
12-
EnableKcp bool `envconfig:"default=true,optional"`
13-
LocalDevelopment bool `envconfig:"default=false,optional"`
14-
}
15-
16-
type Gateway struct {
17-
Port string `envconfig:"default=8080,optional"`
18-
LogLevel string `envconfig:"default=INFO,optional"`
19-
UserNameClaim string `envconfig:"default=email,optional"`
20-
ShouldImpersonate bool `envconfig:"default=true,optional"`
21-
22-
HandlerCfg struct {
23-
Pretty bool `envconfig:"default=true,optional"`
24-
Playground bool `envconfig:"default=true,optional"`
25-
GraphiQL bool `envconfig:"default=true,optional"`
26-
}
27-
28-
Cors struct {
29-
Enabled bool `envconfig:"default=false,optional"`
30-
AllowedOrigins []string `envconfig:"default=*,optional"`
31-
AllowedHeaders []string `envconfig:"default=*,optional"`
32-
}
33-
}
34-
35-
type Listener struct {
36-
MetricsAddr string `envconfig:"default=0,optional"`
37-
EnableLeaderElection bool `envconfig:"default=false,optional"`
38-
ProbeAddr string `envconfig:"default=:8081,optional"`
39-
SecureMetrics bool `envconfig:"default=true,optional"`
40-
EnableHTTP2 bool `envconfig:"default=false,optional"`
41-
ApiExportWorkspace string `envconfig:"default=:root,optional"`
42-
ApiExportName string `envconfig:"default=kubernetes.graphql.gateway,optional"`
43-
}
44-
45-
// NewFromEnv creates a Gateway from environment values
46-
func NewFromEnv() (Config, error) {
47-
cfg := Config{}
48-
err := envconfig.Init(&cfg)
49-
return cfg, err
4+
OpenApiDefinitionsPath string `mapstructure:"openapi-definitions-path"`
5+
EnableKcp bool `mapstructure:"enable-kcp"`
6+
LocalDevelopment bool `mapstructure:"local-development"`
7+
8+
Listener struct {
9+
// Listener fields will be added here
10+
} `mapstructure:",squash"`
11+
12+
Gateway struct {
13+
Port string `mapstructure:"gateway-port"`
14+
UsernameClaim string `mapstructure:"gateway-username-claim"`
15+
ShouldImpersonate bool `mapstructure:"gateway-should-impersonate"`
16+
17+
HandlerCfg struct {
18+
Pretty bool `mapstructure:"gateway-handler-pretty"`
19+
Playground bool `mapstructure:"gateway-handler-playground"`
20+
GraphiQL bool `mapstructure:"gateway-handler-graphiql"`
21+
} `mapstructure:",squash"`
22+
23+
Cors struct {
24+
Enabled bool `mapstructure:"gateway-cors-enabled"`
25+
AllowedOrigins []string `mapstructure:"gateway-cors-allowed-origins"`
26+
AllowedHeaders []string `mapstructure:"gateway-cors-allowed-headers"`
27+
} `mapstructure:",squash"`
28+
} `mapstructure:",squash"`
5029
}

0 commit comments

Comments
 (0)