// Package classification Interlock // // Documentation for the Interlock API // // Schemes: http // BasePath: / // Version: 1.0.0 // Host: interlock.interlock.svc.cluster.local // // Consumes: // - application/json // // Produces: // - application/json // // swagger:meta package interlock import ( "context" "flag" "fmt" "net" "net/http" "os" "slices" "strconv" "github.com/gin-contrib/requestid" "github.com/gin-gonic/gin" "github.com/peterbourgon/ff/v3" "github.com/spf13/afero" "edge-infra.dev/pkg/lib/fog" "edge-infra.dev/pkg/sds/interlock/internal/config" errs "edge-infra.dev/pkg/sds/interlock/internal/errors" "edge-infra.dev/pkg/sds/interlock/internal/middleware" "edge-infra.dev/pkg/sds/interlock/internal/observability" "edge-infra.dev/pkg/sds/interlock/topic/cluster" "edge-infra.dev/pkg/sds/interlock/topic/host" "edge-infra.dev/pkg/sds/interlock/topic/instances" "edge-infra.dev/pkg/sds/interlock/websocket" ) // +kubebuilder:rbac:groups="",resources=nodes;pods,verbs=get;watch;list const ( InterlockAPIPort = 80 InterlockMetricsAPIPort = 8080 ) // EndpointRegistrant is an interface for package to register endpoints to a // router type EndpointRegistrant interface { RegisterEndpoints(*gin.Engine) } // api represents an API provider type api struct { router *gin.Engine port int } // newAPI creates a new API instance with the provided router, middleware and // port func newAPI(port int, router *gin.Engine, middleware ...gin.HandlerFunc) *api { router.Use(middleware...) return &api{ router, port, } } // registerEndpoints registers the 404, 405 and root endpoints alongside all // provided EndpointRegistrant endpoints func (a *api) registerEndpoints(ers ...EndpointRegistrant) { a.router.NoRoute(func(c *gin.Context) { c.JSON(http.StatusNotFound, errs.NewErrorResponse(errs.NewError(http.StatusText(http.StatusNotFound)))) }) a.router.HandleMethodNotAllowed = true a.router.NoMethod(func(c *gin.Context) { c.JSON(http.StatusMethodNotAllowed, errs.NewErrorResponse(errs.NewError(http.StatusText(http.StatusMethodNotAllowed)))) }) for _, er := range ers { er.RegisterEndpoints(a.router) } a.registerRoot() } // registerRoot registers an API index at the root endpoint func (a *api) registerRoot() { endpoints := []string{} for _, route := range a.router.Routes() { if !slices.Contains(endpoints, route.Path) { endpoints = append(endpoints, route.Path) } } a.router.GET("/", func(c *gin.Context) { c.IndentedJSON(200, endpoints) }) } // run the API router on the API port func (a *api) run() error { addr := net.JoinHostPort("", strconv.Itoa(a.port)) return a.router.Run(addr) } // Run the Interlock webserver and start the Interlock API and Interlock metrics // API func Run() error { log := observability.NewLogger() log.Info("starting Interlock API") ctx := fog.IntoContext(context.Background(), log) api := newAPI( InterlockAPIPort, // on port 80 gin.New(), // custom gin engine requestid.New(), middleware.SetLoggerInContext(log), middleware.RequestLogger(), middleware.RequestRecorder(), middleware.SetAccessControlHeaders(), gin.Recovery(), ) fs := afero.NewOsFs() cfg, err := config.New(fs) if err != nil { return err } flags := flag.NewFlagSet("interlock", flag.ExitOnError) cfg.BindFlags(flags) if err := ff.Parse(flags, os.Args[1:], ff.WithEnvVarNoPrefix(), ff.WithIgnoreUndefined(true)); err != nil { return err } websocketManager := websocket.NewManager() cluster, err := cluster.New(ctx, cfg, websocketManager) if err != nil { return fmt.Errorf("failed to set up cluster topic: %w", err) } host, err := host.New(ctx, cfg, websocketManager) if err != nil { return fmt.Errorf("failed to set up host topic: %w", err) } instances, err := instances.New(ctx, cfg, websocketManager) if err != nil { return fmt.Errorf("failed to set up discover topic: %w", err) } api.registerEndpoints( websocketManager, host, cluster, instances, ) metrics := newAPI( InterlockMetricsAPIPort, // on port 8080 gin.Default(), // default gin engine ) metrics.registerEndpoints( observability.NewMetrics(), ) go func() { // run the Interlock metrics API and exit the program if there is an // error if err := metrics.run(); err != nil { log.Error(err, "error while running metrics server") os.Exit(1) } }() go func() { // run the Interlock informers to keep state up to date with API Server if err := cfg.Cache.Start(ctx); err != nil { log.Error(err, "failed to run informers") } }() // run the Interlock API and return the error if any return api.run() } // The request has failed // // swagger:response ErrorResponse type ErrorResponseWrapper struct { // Descriptions of the errors that occurred // // in: body Body Errors `json:"body"` } // Errors returned on API failures type Errors struct { Errors []errs.Error `json:"errors"` }