blob: d075e982b942887b1abf62018aa5fff168240abd [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Serge Bazanskibee272f2022-09-13 13:52:42 +02004package server
5
6import (
7 "context"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +02008 "errors"
Serge Bazanskibee272f2022-09-13 13:52:42 +02009 "flag"
10 "net"
11 "net/http"
12
13 "github.com/improbable-eng/grpc-web/go/grpcweb"
14 "google.golang.org/grpc"
15 "google.golang.org/grpc/codes"
16 "google.golang.org/grpc/credentials/insecure"
17 "google.golang.org/grpc/reflection"
18 "google.golang.org/grpc/status"
19 "k8s.io/klog/v2"
20
21 apb "source.monogon.dev/cloud/api"
Serge Bazanskia5baa872022-09-15 18:49:35 +020022 "source.monogon.dev/cloud/apigw/model"
Serge Bazanskibee272f2022-09-13 13:52:42 +020023 "source.monogon.dev/cloud/lib/component"
24)
25
26// Config is the main configuration of the apigw server. It's usually populated
27// from flags via RegisterFlags, but can also be set manually (eg. in tests).
28type Config struct {
Serge Bazanskia5baa872022-09-15 18:49:35 +020029 Component component.ComponentConfig
30 Database component.CockroachConfig
Serge Bazanskibee272f2022-09-13 13:52:42 +020031
32 PublicListenAddress string
33}
34
35// RegisterFlags registers the component configuration to be provided by flags.
36// This must be called exactly once before then calling flags.Parse().
37func (c *Config) RegisterFlags() {
Serge Bazanskia5baa872022-09-15 18:49:35 +020038 c.Component.RegisterFlags("apigw")
39 c.Database.RegisterFlags("apigw_db")
Serge Bazanskibee272f2022-09-13 13:52:42 +020040 flag.StringVar(&c.PublicListenAddress, "apigw_public_grpc_listen_address", ":8080", "Address to listen at for public/user gRPC connections for apigw")
41}
42
43// Server runs the apigw server. It listens on two interfaces:
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +020044// - Internal gRPC, which is authenticated using TLS and authorized by CA. This
45// is to be used for internal RPCs, eg. management/debug.
46// - Public gRPC-Web, which is currently unauthenticated.
Serge Bazanskibee272f2022-09-13 13:52:42 +020047type Server struct {
48 Config Config
49
50 // ListenGRPC will contain the address at which the internal gRPC server is
51 // listening after .Start() has been called. This can differ from the configured
52 // value if the configuration requests any port (via :0).
53 ListenGRPC string
54 // ListenPublic will contain the address at which the public API server is
55 // listening after .Start() has been called. This can differ from the configured
56 // value if the configuration requests any port (via :0).
57 ListenPublic string
58}
59
60func (s *Server) startInternalGRPC(ctx context.Context) {
Serge Bazanskia5baa872022-09-15 18:49:35 +020061 g := grpc.NewServer(s.Config.Component.GRPCServerOptions()...)
62 lis, err := net.Listen("tcp", s.Config.Component.GRPCListenAddress)
Serge Bazanskibee272f2022-09-13 13:52:42 +020063 if err != nil {
64 klog.Exitf("Could not listen: %v", err)
65 }
66 s.ListenGRPC = lis.Addr().String()
67
68 reflection.Register(g)
69
70 klog.Infof("Internal gRPC listening on %s", s.ListenGRPC)
71 go func() {
72 err := g.Serve(lis)
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +020073 if !errors.Is(err, ctx.Err()) {
Serge Bazanskibee272f2022-09-13 13:52:42 +020074 klog.Exitf("Internal gRPC serve failed: %v", err)
75 }
76 }()
77}
78
79func (s *Server) startPublic(ctx context.Context) {
80 g := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
81 lis, err := net.Listen("tcp", s.Config.PublicListenAddress)
82 if err != nil {
83 klog.Exitf("Could not listen: %v", err)
84 }
85 s.ListenPublic = lis.Addr().String()
86
87 reflection.Register(g)
88 apb.RegisterIAMServer(g, s)
89
90 wrapped := grpcweb.WrapServer(g)
91 server := http.Server{
92 Addr: s.Config.PublicListenAddress,
93 Handler: http.HandlerFunc(wrapped.ServeHTTP),
94 }
95 klog.Infof("Public API listening on %s", s.ListenPublic)
96 go func() {
97 err := server.Serve(lis)
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +020098 if !errors.Is(err, ctx.Err()) {
Serge Bazanskibee272f2022-09-13 13:52:42 +020099 klog.Exitf("Public API serve failed: %v", err)
100 }
101 }()
102}
103
104// Start runs the two listeners of the server. The process will fail (via
105// klog.Exit) if any of the listeners/servers fail to start.
106func (s *Server) Start(ctx context.Context) {
Serge Bazanskia5baa872022-09-15 18:49:35 +0200107 if s.Config.Database.Migrations == nil {
108 klog.Infof("Using default migrations source.")
109 m, err := model.MigrationsSource()
110 if err != nil {
Tim Windelschmidt690511d2024-04-22 19:10:29 +0200111 klog.Exitf("failed to prepare migrations source: %v", err)
Serge Bazanskia5baa872022-09-15 18:49:35 +0200112 }
113 s.Config.Database.Migrations = m
114 }
115
116 klog.Infof("Running migrations...")
117 if err := s.Config.Database.MigrateUp(); err != nil {
118 klog.Exitf("Migrations failed: %v", err)
119 }
120 klog.Infof("Migrations done.")
Serge Bazanskibee272f2022-09-13 13:52:42 +0200121 s.startInternalGRPC(ctx)
122 s.startPublic(ctx)
123}
124
125func (s *Server) WhoAmI(ctx context.Context, req *apb.WhoAmIRequest) (*apb.WhoAmIResponse, error) {
126 klog.Infof("req: %+v", req)
127 return nil, status.Error(codes.Unimplemented, "unimplemented")
128}