1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package interlock
18
19 import (
20 "context"
21 "flag"
22 "fmt"
23 "net"
24 "net/http"
25 "os"
26 "slices"
27 "strconv"
28
29 "github.com/gin-contrib/requestid"
30 "github.com/gin-gonic/gin"
31 "github.com/peterbourgon/ff/v3"
32 "github.com/spf13/afero"
33
34 "edge-infra.dev/pkg/lib/fog"
35 "edge-infra.dev/pkg/sds/interlock/internal/config"
36 errs "edge-infra.dev/pkg/sds/interlock/internal/errors"
37 "edge-infra.dev/pkg/sds/interlock/internal/middleware"
38 "edge-infra.dev/pkg/sds/interlock/internal/observability"
39 "edge-infra.dev/pkg/sds/interlock/topic/cluster"
40 "edge-infra.dev/pkg/sds/interlock/topic/host"
41 "edge-infra.dev/pkg/sds/interlock/topic/instances"
42 "edge-infra.dev/pkg/sds/interlock/websocket"
43 )
44
45
46
47 const (
48 InterlockAPIPort = 80
49 InterlockMetricsAPIPort = 8080
50 )
51
52
53
54 type EndpointRegistrant interface {
55 RegisterEndpoints(*gin.Engine)
56 }
57
58
59 type api struct {
60 router *gin.Engine
61 port int
62 }
63
64
65
66 func newAPI(port int, router *gin.Engine, middleware ...gin.HandlerFunc) *api {
67 router.Use(middleware...)
68 return &api{
69 router,
70 port,
71 }
72 }
73
74
75
76 func (a *api) registerEndpoints(ers ...EndpointRegistrant) {
77 a.router.NoRoute(func(c *gin.Context) {
78 c.JSON(http.StatusNotFound, errs.NewErrorResponse(errs.NewError(http.StatusText(http.StatusNotFound))))
79 })
80 a.router.HandleMethodNotAllowed = true
81 a.router.NoMethod(func(c *gin.Context) {
82 c.JSON(http.StatusMethodNotAllowed, errs.NewErrorResponse(errs.NewError(http.StatusText(http.StatusMethodNotAllowed))))
83 })
84 for _, er := range ers {
85 er.RegisterEndpoints(a.router)
86 }
87 a.registerRoot()
88 }
89
90
91 func (a *api) registerRoot() {
92 endpoints := []string{}
93 for _, route := range a.router.Routes() {
94 if !slices.Contains(endpoints, route.Path) {
95 endpoints = append(endpoints, route.Path)
96 }
97 }
98 a.router.GET("/", func(c *gin.Context) {
99 c.IndentedJSON(200, endpoints)
100 })
101 }
102
103
104 func (a *api) run() error {
105 addr := net.JoinHostPort("", strconv.Itoa(a.port))
106 return a.router.Run(addr)
107 }
108
109
110
111 func Run() error {
112 log := observability.NewLogger()
113 log.Info("starting Interlock API")
114 ctx := fog.IntoContext(context.Background(), log)
115
116 api := newAPI(
117 InterlockAPIPort,
118 gin.New(),
119 requestid.New(),
120 middleware.SetLoggerInContext(log),
121 middleware.RequestLogger(),
122 middleware.RequestRecorder(),
123 middleware.SetAccessControlHeaders(),
124 gin.Recovery(),
125 )
126
127 fs := afero.NewOsFs()
128 cfg, err := config.New(fs)
129 if err != nil {
130 return err
131 }
132 flags := flag.NewFlagSet("interlock", flag.ExitOnError)
133 cfg.BindFlags(flags)
134 if err := ff.Parse(flags, os.Args[1:], ff.WithEnvVarNoPrefix(), ff.WithIgnoreUndefined(true)); err != nil {
135 return err
136 }
137
138 websocketManager := websocket.NewManager()
139 cluster, err := cluster.New(ctx, cfg, websocketManager)
140 if err != nil {
141 return fmt.Errorf("failed to set up cluster topic: %w", err)
142 }
143 host, err := host.New(ctx, cfg, websocketManager)
144 if err != nil {
145 return fmt.Errorf("failed to set up host topic: %w", err)
146 }
147 instances, err := instances.New(ctx, cfg, websocketManager)
148 if err != nil {
149 return fmt.Errorf("failed to set up discover topic: %w", err)
150 }
151
152 api.registerEndpoints(
153 websocketManager,
154 host,
155 cluster,
156 instances,
157 )
158
159 metrics := newAPI(
160 InterlockMetricsAPIPort,
161 gin.Default(),
162 )
163 metrics.registerEndpoints(
164 observability.NewMetrics(),
165 )
166 go func() {
167
168
169 if err := metrics.run(); err != nil {
170 log.Error(err, "error while running metrics server")
171 os.Exit(1)
172 }
173 }()
174 go func() {
175
176 if err := cfg.Cache.Start(ctx); err != nil {
177 log.Error(err, "failed to run informers")
178 }
179 }()
180
181 return api.run()
182 }
183
184
185
186
187 type ErrorResponseWrapper struct {
188
189
190
191 Body Errors `json:"body"`
192 }
193
194
195 type Errors struct {
196 Errors []errs.Error `json:"errors"`
197 }
198
View as plain text