diff --git a/internal/server/api.go b/internal/server/api.go index 19618ef..33701c2 100644 --- a/internal/server/api.go +++ b/internal/server/api.go @@ -4,12 +4,14 @@ import ( "context" "errors" "fmt" + "regexp" "strings" "time" "connectrpc.com/connect" "github.com/postfinance/discovery" "github.com/postfinance/discovery/internal/auth" + "github.com/postfinance/discovery/internal/exporter" "github.com/postfinance/discovery/internal/registry" "github.com/postfinance/discovery/internal/repo" "github.com/postfinance/discovery/internal/server/convert" @@ -20,6 +22,7 @@ import ( ) var ( + _ discoveryv1connect.ServiceAPIHandler = (*API)(nil) _ discoveryv1connect.ServerAPIHandler = (*API)(nil) _ discoveryv1connect.NamespaceAPIHandler = (*API)(nil) _ discoveryv1connect.TokenAPIHandler = (*API)(nil) @@ -31,6 +34,124 @@ type API struct { tokenHandler *auth.TokenHandler } +// ListService implements discoveryv1connect.ServiceAPIHandler. +func (a *API) ListService(_ context.Context, req *connect.Request[discoveryv1.ListServiceRequest]) (*connect.Response[discoveryv1.ListServiceResponse], error) { + s, err := a.r.ListService(req.Msg.GetNamespace(), "") + if err != nil { + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("could not list services: %w", err)) + } + + resp := connect.NewResponse(&discoveryv1.ListServiceResponse{ + Services: convert.ServicesToPB(s), + }) + + return resp, nil +} + +// ListTargetGroup implements discoveryv1connect.ServiceAPIHandler. +func (a *API) ListTargetGroup(_ context.Context, in *connect.Request[discoveryv1.ListTargetGroupRequest]) (*connect.Response[discoveryv1.ListTargetGroupResponse], error) { + var config discovery.ExportConfig + + switch in.Msg.GetConfig() { + case "standard": + config = discovery.Standard + case "blackbox": + config = discovery.Blackbox + case "": + config = discovery.Standard + default: + return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid exporter config: '%s'", in.Msg.GetConfig())) + } + + s, err := a.r.ListService(in.Msg.GetNamespace(), "") + if err != nil { + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("could not list services: %w", err)) + } + + serverFilter, err := regexp.Compile(fmt.Sprintf(`^%s$`, in.Msg.GetServer())) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid regular expression: '%s'", in.Msg.GetServer())) + } + + s = s.Filter(discovery.ServiceByServer(serverFilter)) + + t := make([]*discoveryv1.TargetGroup, 0, len(s)) + + for i := range s { + tg := exporter.NewTargetGroup(s[i], config) + t = append(t, convert.TargetGroupToPB(&tg)) + } + + resp := connect.NewResponse(&discoveryv1.ListTargetGroupResponse{ + Targetgroups: t, + }) + + return resp, nil +} + +// RegisterService implements discoveryv1connect.ServiceAPIHandler. +func (a *API) RegisterService(ctx context.Context, req *connect.Request[discoveryv1.RegisterServiceRequest]) (*connect.Response[discoveryv1.RegisterServiceResponse], error) { + if err := verifyUser(ctx, req.Msg.GetNamespace()); err != nil { + return nil, err + } + + s, err := discovery.NewService(req.Msg.GetName(), req.Msg.GetEndpoint()) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("service with endpoint %s is invalid: %w", req.Msg.GetEndpoint(), err)) + } + + s.Labels = req.Msg.GetLabels() + s.Description = req.Msg.GetDescription() + s.Selector = req.Msg.GetSelector() + + if req.Msg.GetNamespace() != "" { + s.Namespace = req.Msg.GetNamespace() + } + + svc, err := a.r.RegisterService(*s) + if err != nil { + if registry.IsServersNotFound(err) { + return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("no server found for selector '%s'", req.Msg.GetSelector())) + } + + if registry.IsNamespaceNotFound(err) { + return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("namespace '%s' not found", req.Msg.GetNamespace())) + } + + if registry.IsValidationError(err) { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("could not register service %s in store: %w", req.Msg.GetEndpoint(), err)) + } + + resp := connect.NewResponse(&discoveryv1.RegisterServiceResponse{ + Service: convert.ServiceToPB(svc), + }) + + return resp, nil +} + +// UnRegisterService implements discoveryv1connect.ServiceAPIHandler. +func (a *API) UnRegisterService(ctx context.Context, req *connect.Request[discoveryv1.UnRegisterServiceRequest]) (*connect.Response[discoveryv1.UnRegisterServiceResponse], error) { + if err := verifyUser(ctx, req.Msg.GetNamespace()); err != nil { + return nil, err + } + + if err := a.r.UnRegisterService(req.Msg.GetId(), req.Msg.GetNamespace()); err != nil { + c := connect.CodeInternal + if errors.Is(err, repo.ErrNotFound) { + c = connect.CodeNotFound + } + + return nil, connect.NewError(c, fmt.Errorf("could not unregister service %s in namespace %s: %w", req.Msg.GetId(), req.Msg.GetNamespace(), err)) + } + + resp := connect.NewResponse(&discoveryv1.UnRegisterServiceResponse{}) + + return resp, nil +} + // Create implements discoveryv1connect.TokenAPIHandler. func (a *API) Create(_ context.Context, req *connect.Request[discoveryv1.CreateRequest]) (*connect.Response[discoveryv1.CreateResponse], error) { var expiry time.Duration @@ -107,6 +228,8 @@ func (a *API) RegisterServer(_ context.Context, req *connect.Request[discoveryv1 } // UnregisterServer implements discoveryv1connect.ServerAPIHandler. +// +//nolint:dupl // is ok func (a *API) UnregisterServer(_ context.Context, req *connect.Request[discoveryv1.UnregisterServerRequest]) (*connect.Response[discoveryv1.UnregisterServerResponse], error) { if err := a.r.UnRegisterServer(req.Msg.GetName()); err != nil { c := connect.CodeInternal @@ -159,6 +282,8 @@ func (a *API) RegisterNamespace(_ context.Context, req *connect.Request[discover } // UnregisterNamespace implements discoveryv1connect.NamespaceAPIHandler. +// +//nolint:dupl // is ok func (a *API) UnregisterNamespace(_ context.Context, req *connect.Request[discoveryv1.UnregisterNamespaceRequest]) (*connect.Response[discoveryv1.UnregisterNamespaceResponse], error) { if err := a.r.UnRegisterNamespace(req.Msg.GetName()); err != nil { c := connect.CodeInternal diff --git a/internal/server/server.go b/internal/server/server.go index 618dd42..2135441 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -118,6 +118,8 @@ func (s *Server) createMux(api *API) *http.ServeMux { mux.Handle(httpPath, handler) httpPath, handler = discoveryv1connect.NewTokenAPIHandler(api) mux.Handle(httpPath, handler) + httpPath, handler = discoveryv1connect.NewServiceAPIHandler(api) + mux.Handle(httpPath, handler) return mux }