blob: 902b4e00bda62c19225492aec3fc4585f88ea4aa [file] [log] [blame]
Serge Bazanskibee272f2022-09-13 13:52:42 +02001package server
2
3import (
4 "context"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +02005 "errors"
Serge Bazanskibee272f2022-09-13 13:52:42 +02006 "flag"
7 "net"
8 "net/http"
9
10 "github.com/improbable-eng/grpc-web/go/grpcweb"
11 "google.golang.org/grpc"
12 "google.golang.org/grpc/codes"
13 "google.golang.org/grpc/credentials/insecure"
14 "google.golang.org/grpc/reflection"
15 "google.golang.org/grpc/status"
16 "k8s.io/klog/v2"
17
18 apb "source.monogon.dev/cloud/api"
Serge Bazanskia5baa872022-09-15 18:49:35 +020019 "source.monogon.dev/cloud/apigw/model"
Serge Bazanskibee272f2022-09-13 13:52:42 +020020 "source.monogon.dev/cloud/lib/component"
21)
22
23// Config is the main configuration of the apigw server. It's usually populated
24// from flags via RegisterFlags, but can also be set manually (eg. in tests).
25type Config struct {
Serge Bazanskia5baa872022-09-15 18:49:35 +020026 Component component.ComponentConfig
27 Database component.CockroachConfig
Serge Bazanskibee272f2022-09-13 13:52:42 +020028
29 PublicListenAddress string
30}
31
32// RegisterFlags registers the component configuration to be provided by flags.
33// This must be called exactly once before then calling flags.Parse().
34func (c *Config) RegisterFlags() {
Serge Bazanskia5baa872022-09-15 18:49:35 +020035 c.Component.RegisterFlags("apigw")
36 c.Database.RegisterFlags("apigw_db")
Serge Bazanskibee272f2022-09-13 13:52:42 +020037 flag.StringVar(&c.PublicListenAddress, "apigw_public_grpc_listen_address", ":8080", "Address to listen at for public/user gRPC connections for apigw")
38}
39
40// Server runs the apigw server. It listens on two interfaces:
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +020041// - Internal gRPC, which is authenticated using TLS and authorized by CA. This
42// is to be used for internal RPCs, eg. management/debug.
43// - Public gRPC-Web, which is currently unauthenticated.
Serge Bazanskibee272f2022-09-13 13:52:42 +020044type Server struct {
45 Config Config
46
47 // ListenGRPC will contain the address at which the internal gRPC server is
48 // listening after .Start() has been called. This can differ from the configured
49 // value if the configuration requests any port (via :0).
50 ListenGRPC string
51 // ListenPublic will contain the address at which the public API server is
52 // listening after .Start() has been called. This can differ from the configured
53 // value if the configuration requests any port (via :0).
54 ListenPublic string
55}
56
57func (s *Server) startInternalGRPC(ctx context.Context) {
Serge Bazanskia5baa872022-09-15 18:49:35 +020058 g := grpc.NewServer(s.Config.Component.GRPCServerOptions()...)
59 lis, err := net.Listen("tcp", s.Config.Component.GRPCListenAddress)
Serge Bazanskibee272f2022-09-13 13:52:42 +020060 if err != nil {
61 klog.Exitf("Could not listen: %v", err)
62 }
63 s.ListenGRPC = lis.Addr().String()
64
65 reflection.Register(g)
66
67 klog.Infof("Internal gRPC listening on %s", s.ListenGRPC)
68 go func() {
69 err := g.Serve(lis)
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +020070 if !errors.Is(err, ctx.Err()) {
Serge Bazanskibee272f2022-09-13 13:52:42 +020071 klog.Exitf("Internal gRPC serve failed: %v", err)
72 }
73 }()
74}
75
76func (s *Server) startPublic(ctx context.Context) {
77 g := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
78 lis, err := net.Listen("tcp", s.Config.PublicListenAddress)
79 if err != nil {
80 klog.Exitf("Could not listen: %v", err)
81 }
82 s.ListenPublic = lis.Addr().String()
83
84 reflection.Register(g)
85 apb.RegisterIAMServer(g, s)
86
87 wrapped := grpcweb.WrapServer(g)
88 server := http.Server{
89 Addr: s.Config.PublicListenAddress,
90 Handler: http.HandlerFunc(wrapped.ServeHTTP),
91 }
92 klog.Infof("Public API listening on %s", s.ListenPublic)
93 go func() {
94 err := server.Serve(lis)
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +020095 if !errors.Is(err, ctx.Err()) {
Serge Bazanskibee272f2022-09-13 13:52:42 +020096 klog.Exitf("Public API serve failed: %v", err)
97 }
98 }()
99}
100
101// Start runs the two listeners of the server. The process will fail (via
102// klog.Exit) if any of the listeners/servers fail to start.
103func (s *Server) Start(ctx context.Context) {
Serge Bazanskia5baa872022-09-15 18:49:35 +0200104 if s.Config.Database.Migrations == nil {
105 klog.Infof("Using default migrations source.")
106 m, err := model.MigrationsSource()
107 if err != nil {
Tim Windelschmidt690511d2024-04-22 19:10:29 +0200108 klog.Exitf("failed to prepare migrations source: %v", err)
Serge Bazanskia5baa872022-09-15 18:49:35 +0200109 }
110 s.Config.Database.Migrations = m
111 }
112
113 klog.Infof("Running migrations...")
114 if err := s.Config.Database.MigrateUp(); err != nil {
115 klog.Exitf("Migrations failed: %v", err)
116 }
117 klog.Infof("Migrations done.")
Serge Bazanskibee272f2022-09-13 13:52:42 +0200118 s.startInternalGRPC(ctx)
119 s.startPublic(ctx)
120}
121
122func (s *Server) WhoAmI(ctx context.Context, req *apb.WhoAmIRequest) (*apb.WhoAmIResponse, error) {
123 klog.Infof("req: %+v", req)
124 return nil, status.Error(codes.Unimplemented, "unimplemented")
125}