github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/cmd/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"net/http"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/authzed/consistent"
    13  	"github.com/authzed/grpcutil"
    14  	"github.com/cespare/xxhash/v2"
    15  	"github.com/ecordell/optgen/helpers"
    16  	grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
    17  	"github.com/hashicorp/go-multierror"
    18  	"github.com/prometheus/client_golang/prometheus"
    19  	"github.com/rs/cors"
    20  	"github.com/rs/zerolog"
    21  	"github.com/sean-/sysexits"
    22  	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
    23  	"golang.org/x/sync/errgroup"
    24  	"google.golang.org/grpc"
    25  	_ "google.golang.org/grpc/encoding/gzip" // enable gzip compression on all derivative servers
    26  
    27  	"github.com/authzed/spicedb/internal/auth"
    28  	"github.com/authzed/spicedb/internal/datastore/proxy"
    29  	"github.com/authzed/spicedb/internal/datastore/proxy/schemacaching"
    30  	"github.com/authzed/spicedb/internal/dispatch"
    31  	clusterdispatch "github.com/authzed/spicedb/internal/dispatch/cluster"
    32  	combineddispatch "github.com/authzed/spicedb/internal/dispatch/combined"
    33  	"github.com/authzed/spicedb/internal/dispatch/graph"
    34  	"github.com/authzed/spicedb/internal/gateway"
    35  	log "github.com/authzed/spicedb/internal/logging"
    36  	"github.com/authzed/spicedb/internal/services"
    37  	dispatchSvc "github.com/authzed/spicedb/internal/services/dispatch"
    38  	"github.com/authzed/spicedb/internal/services/health"
    39  	v1svc "github.com/authzed/spicedb/internal/services/v1"
    40  	"github.com/authzed/spicedb/internal/telemetry"
    41  	datastorecfg "github.com/authzed/spicedb/pkg/cmd/datastore"
    42  	"github.com/authzed/spicedb/pkg/cmd/util"
    43  	"github.com/authzed/spicedb/pkg/datastore"
    44  	"github.com/authzed/spicedb/pkg/middleware/requestid"
    45  	"github.com/authzed/spicedb/pkg/spiceerrors"
    46  )
    47  
    48  // ConsistentHashringBuilder is a balancer Builder that uses xxhash as the
    49  // underlying hash for the ConsistentHashringBalancers it creates.
    50  var ConsistentHashringBuilder = consistent.NewBuilder(xxhash.Sum64)
    51  
    52  //go:generate go run github.com/ecordell/optgen -output zz_generated.options.go . Config
    53  type Config struct {
    54  	// API config
    55  	GRPCServer             util.GRPCServerConfig `debugmap:"visible"`
    56  	GRPCAuthFunc           grpc_auth.AuthFunc    `debugmap:"visible"`
    57  	PresharedSecureKey     []string              `debugmap:"sensitive"`
    58  	ShutdownGracePeriod    time.Duration         `debugmap:"visible"`
    59  	DisableVersionResponse bool                  `debugmap:"visible"`
    60  
    61  	// GRPC Gateway config
    62  	HTTPGateway                    util.HTTPServerConfig `debugmap:"visible"`
    63  	HTTPGatewayUpstreamAddr        string                `debugmap:"visible"`
    64  	HTTPGatewayUpstreamTLSCertPath string                `debugmap:"visible"`
    65  	HTTPGatewayCorsEnabled         bool                  `debugmap:"visible"`
    66  	HTTPGatewayCorsAllowedOrigins  []string              `debugmap:"visible-format"`
    67  
    68  	// Datastore
    69  	DatastoreConfig datastorecfg.Config `debugmap:"visible"`
    70  	Datastore       datastore.Datastore `debugmap:"visible"`
    71  
    72  	// Datastore usage
    73  	MaxCaveatContextSize       int `debugmap:"visible" default:"4096"`
    74  	MaxRelationshipContextSize int `debugmap:"visible" default:"25_000"`
    75  
    76  	// Namespace cache
    77  	EnableExperimentalWatchableSchemaCache bool          `debugmap:"visible"`
    78  	SchemaWatchHeartbeat                   time.Duration `debugmap:"visible"`
    79  	NamespaceCacheConfig                   CacheConfig   `debugmap:"visible"`
    80  
    81  	// Schema options
    82  	SchemaPrefixesRequired bool `debugmap:"visible"`
    83  
    84  	// Dispatch options
    85  	DispatchServer                    util.GRPCServerConfig   `debugmap:"visible"`
    86  	DispatchMaxDepth                  uint32                  `debugmap:"visible"`
    87  	GlobalDispatchConcurrencyLimit    uint16                  `debugmap:"visible"`
    88  	DispatchConcurrencyLimits         graph.ConcurrencyLimits `debugmap:"visible"`
    89  	DispatchUpstreamAddr              string                  `debugmap:"visible"`
    90  	DispatchUpstreamCAPath            string                  `debugmap:"visible"`
    91  	DispatchUpstreamTimeout           time.Duration           `debugmap:"visible"`
    92  	DispatchClientMetricsEnabled      bool                    `debugmap:"visible"`
    93  	DispatchClientMetricsPrefix       string                  `debugmap:"visible"`
    94  	DispatchClusterMetricsEnabled     bool                    `debugmap:"visible"`
    95  	DispatchClusterMetricsPrefix      string                  `debugmap:"visible"`
    96  	Dispatcher                        dispatch.Dispatcher     `debugmap:"visible"`
    97  	DispatchHashringReplicationFactor uint16                  `debugmap:"visible"`
    98  	DispatchHashringSpread            uint8                   `debugmap:"visible"`
    99  
   100  	DispatchSecondaryUpstreamAddrs map[string]string `debugmap:"visible"`
   101  	DispatchSecondaryUpstreamExprs map[string]string `debugmap:"visible"`
   102  
   103  	DispatchCacheConfig        CacheConfig `debugmap:"visible"`
   104  	ClusterDispatchCacheConfig CacheConfig `debugmap:"visible"`
   105  
   106  	// API Behavior
   107  	DisableV1SchemaAPI              bool          `debugmap:"visible"`
   108  	V1SchemaAdditiveOnly            bool          `debugmap:"visible"`
   109  	MaximumUpdatesPerWrite          uint16        `debugmap:"visible"`
   110  	MaximumPreconditionCount        uint16        `debugmap:"visible"`
   111  	MaxDatastoreReadPageSize        uint64        `debugmap:"visible"`
   112  	StreamingAPITimeout             time.Duration `debugmap:"visible"`
   113  	WatchHeartbeat                  time.Duration `debugmap:"visible"`
   114  	MaxReadRelationshipsLimit       uint32        `debugmap:"visible"`
   115  	MaxDeleteRelationshipsLimit     uint32        `debugmap:"visible"`
   116  	MaxLookupResourcesLimit         uint32        `debugmap:"visible"`
   117  	MaxBulkExportRelationshipsLimit uint32        `debugmap:"visible"`
   118  
   119  	// Additional Services
   120  	MetricsAPI util.HTTPServerConfig `debugmap:"visible"`
   121  
   122  	// Middleware for grpc API
   123  	UnaryMiddlewareModification     []MiddlewareModification[grpc.UnaryServerInterceptor]  `debugmap:"hidden"`
   124  	StreamingMiddlewareModification []MiddlewareModification[grpc.StreamServerInterceptor] `debugmap:"hidden"`
   125  
   126  	// Middleware for internal dispatch API
   127  	DispatchUnaryMiddleware     []grpc.UnaryServerInterceptor  `debugmap:"hidden"`
   128  	DispatchStreamingMiddleware []grpc.StreamServerInterceptor `debugmap:"hidden"`
   129  
   130  	// Telemetry
   131  	SilentlyDisableTelemetry bool          `debugmap:"visible"`
   132  	TelemetryCAOverridePath  string        `debugmap:"visible"`
   133  	TelemetryEndpoint        string        `debugmap:"visible"`
   134  	TelemetryInterval        time.Duration `debugmap:"visible"`
   135  
   136  	// Logs
   137  	EnableRequestLogs  bool `debugmap:"visible"`
   138  	EnableResponseLogs bool `debugmap:"visible"`
   139  
   140  	// Metrics
   141  	DisableGRPCLatencyHistogram bool `debugmap:"visible"`
   142  }
   143  
   144  type closeableStack struct {
   145  	closers []func() error
   146  }
   147  
   148  func (c *closeableStack) AddWithError(closer func() error) {
   149  	c.closers = append(c.closers, closer)
   150  }
   151  
   152  func (c *closeableStack) AddCloser(closer io.Closer) {
   153  	if closer != nil {
   154  		c.closers = append(c.closers, closer.Close)
   155  	}
   156  }
   157  
   158  func (c *closeableStack) AddWithoutError(closer func()) {
   159  	c.closers = append(c.closers, func() error {
   160  		closer()
   161  		return nil
   162  	})
   163  }
   164  
   165  func (c *closeableStack) Close() error {
   166  	var err error
   167  	// closer in reverse order how it's expected in deferred funcs
   168  	for i := len(c.closers) - 1; i >= 0; i-- {
   169  		if closerErr := c.closers[i](); closerErr != nil {
   170  			err = multierror.Append(err, closerErr)
   171  		}
   172  	}
   173  	return err
   174  }
   175  
   176  func (c *closeableStack) CloseIfError(err error) error {
   177  	if err != nil {
   178  		return c.Close()
   179  	}
   180  	return nil
   181  }
   182  
   183  // Complete validates the config and fills out defaults.
   184  // if there is no error, a completedServerConfig (with limited options for
   185  // mutation) is returned.
   186  func (c *Config) Complete(ctx context.Context) (RunnableServer, error) {
   187  	log.Ctx(ctx).Info().Fields(helpers.Flatten(c.DebugMap())).Msg("configuration")
   188  
   189  	closeables := closeableStack{}
   190  	var err error
   191  	defer func() {
   192  		// if an error happens during the execution of Complete, all resources are cleaned up
   193  		if closeableErr := closeables.CloseIfError(err); closeableErr != nil {
   194  			log.Ctx(ctx).Err(closeableErr).Msg("failed to clean up resources on Config.Complete")
   195  		}
   196  	}()
   197  
   198  	if len(c.PresharedSecureKey) < 1 && c.GRPCAuthFunc == nil {
   199  		return nil, fmt.Errorf("a preshared key must be provided to authenticate API requests")
   200  	}
   201  
   202  	if c.GRPCAuthFunc == nil {
   203  		log.Ctx(ctx).Trace().Int("preshared-keys-count", len(c.PresharedSecureKey)).Msg("using gRPC auth with preshared key(s)")
   204  		for index, presharedKey := range c.PresharedSecureKey {
   205  			if len(presharedKey) == 0 {
   206  				return nil, fmt.Errorf("preshared key #%d is empty", index+1)
   207  			}
   208  
   209  			log.Ctx(ctx).Trace().Int("preshared-key-"+strconv.Itoa(index+1)+"-length", len(presharedKey)).Msg("preshared key configured")
   210  		}
   211  
   212  		c.GRPCAuthFunc = auth.MustRequirePresharedKey(c.PresharedSecureKey)
   213  	} else {
   214  		log.Ctx(ctx).Trace().Msg("using preconfigured auth function")
   215  	}
   216  
   217  	ds := c.Datastore
   218  	if ds == nil {
   219  		var err error
   220  		ds, err = datastorecfg.NewDatastore(context.Background(), c.DatastoreConfig.ToOption())
   221  		if err != nil {
   222  			return nil, spiceerrors.NewTerminationErrorBuilder(fmt.Errorf("failed to create datastore: %w", err)).
   223  				Component("datastore").
   224  				ExitCode(sysexits.Config).
   225  				Error()
   226  		}
   227  	}
   228  	closeables.AddWithError(ds.Close)
   229  
   230  	nscc, err := c.NamespaceCacheConfig.Complete()
   231  	if err != nil {
   232  		return nil, fmt.Errorf("failed to create namespace cache: %w", err)
   233  	}
   234  	log.Ctx(ctx).Info().EmbedObject(nscc).Msg("configured namespace cache")
   235  
   236  	cachingMode := schemacaching.JustInTimeCaching
   237  	if c.EnableExperimentalWatchableSchemaCache {
   238  		cachingMode = schemacaching.WatchIfSupported
   239  	}
   240  
   241  	ds = proxy.NewObservableDatastoreProxy(ds)
   242  	ds = proxy.NewSingleflightDatastoreProxy(ds)
   243  	ds = schemacaching.NewCachingDatastoreProxy(ds, nscc, c.DatastoreConfig.GCWindow, cachingMode, c.SchemaWatchHeartbeat)
   244  	closeables.AddWithError(ds.Close)
   245  
   246  	specificConcurrencyLimits := c.DispatchConcurrencyLimits
   247  	concurrencyLimits := specificConcurrencyLimits.WithOverallDefaultLimit(c.GlobalDispatchConcurrencyLimit)
   248  
   249  	dispatcher := c.Dispatcher
   250  	if dispatcher == nil {
   251  		cc, err := c.DispatchCacheConfig.WithRevisionParameters(
   252  			c.DatastoreConfig.RevisionQuantization,
   253  			c.DatastoreConfig.FollowerReadDelay,
   254  			c.DatastoreConfig.MaxRevisionStalenessPercent,
   255  		).Complete()
   256  		if err != nil {
   257  			return nil, fmt.Errorf("failed to create dispatcher: %w", err)
   258  		}
   259  		closeables.AddWithoutError(cc.Close)
   260  		log.Ctx(ctx).Info().EmbedObject(cc).Msg("configured dispatch cache")
   261  
   262  		dispatchPresharedKey := ""
   263  		if len(c.PresharedSecureKey) > 0 {
   264  			dispatchPresharedKey = c.PresharedSecureKey[0]
   265  		}
   266  
   267  		hashringConfigJSON, err := (&consistent.BalancerConfig{
   268  			ReplicationFactor: c.DispatchHashringReplicationFactor,
   269  			Spread:            c.DispatchHashringSpread,
   270  		}).ServiceConfigJSON()
   271  		if err != nil {
   272  			return nil, fmt.Errorf("failed to create gRPC hashring balancer config: %w", err)
   273  		}
   274  
   275  		dispatcher, err = combineddispatch.NewDispatcher(
   276  			combineddispatch.UpstreamAddr(c.DispatchUpstreamAddr),
   277  			combineddispatch.UpstreamCAPath(c.DispatchUpstreamCAPath),
   278  			combineddispatch.SecondaryUpstreamAddrs(c.DispatchSecondaryUpstreamAddrs),
   279  			combineddispatch.SecondaryUpstreamExprs(c.DispatchSecondaryUpstreamExprs),
   280  			combineddispatch.GrpcPresharedKey(dispatchPresharedKey),
   281  			combineddispatch.GrpcDialOpts(
   282  				grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
   283  				grpc.WithDefaultServiceConfig(hashringConfigJSON),
   284  				grpc.WithChainUnaryInterceptor(
   285  					requestid.UnaryClientInterceptor(),
   286  				),
   287  				grpc.WithChainStreamInterceptor(
   288  					requestid.StreamClientInterceptor(),
   289  				),
   290  			),
   291  			combineddispatch.MetricsEnabled(c.DispatchClientMetricsEnabled),
   292  			combineddispatch.PrometheusSubsystem(c.DispatchClientMetricsPrefix),
   293  			combineddispatch.Cache(cc),
   294  			combineddispatch.ConcurrencyLimits(concurrencyLimits),
   295  		)
   296  		if err != nil {
   297  			return nil, fmt.Errorf("failed to create dispatcher: %w", err)
   298  		}
   299  
   300  		log.Ctx(ctx).Info().EmbedObject(concurrencyLimits).RawJSON("balancerconfig", []byte(hashringConfigJSON)).Msg("configured dispatcher")
   301  	}
   302  	closeables.AddWithError(dispatcher.Close)
   303  
   304  	if len(c.DispatchUnaryMiddleware) == 0 && len(c.DispatchStreamingMiddleware) == 0 {
   305  		if c.GRPCAuthFunc == nil {
   306  			c.DispatchUnaryMiddleware, c.DispatchStreamingMiddleware = DefaultDispatchMiddleware(log.Logger, auth.MustRequirePresharedKey(c.PresharedSecureKey), ds, c.DisableGRPCLatencyHistogram)
   307  		} else {
   308  			c.DispatchUnaryMiddleware, c.DispatchStreamingMiddleware = DefaultDispatchMiddleware(log.Logger, c.GRPCAuthFunc, ds, c.DisableGRPCLatencyHistogram)
   309  		}
   310  	}
   311  
   312  	var cachingClusterDispatch dispatch.Dispatcher
   313  	if c.DispatchServer.Enabled {
   314  		cdcc, err := c.ClusterDispatchCacheConfig.WithRevisionParameters(
   315  			c.DatastoreConfig.RevisionQuantization,
   316  			c.DatastoreConfig.FollowerReadDelay,
   317  			c.DatastoreConfig.MaxRevisionStalenessPercent,
   318  		).Complete()
   319  		if err != nil {
   320  			return nil, fmt.Errorf("failed to configure cluster dispatch: %w", err)
   321  		}
   322  		log.Ctx(ctx).Info().EmbedObject(cdcc).Msg("configured cluster dispatch cache")
   323  		closeables.AddWithoutError(cdcc.Close)
   324  
   325  		cachingClusterDispatch, err = clusterdispatch.NewClusterDispatcher(
   326  			dispatcher,
   327  			clusterdispatch.MetricsEnabled(c.DispatchClusterMetricsEnabled),
   328  			clusterdispatch.PrometheusSubsystem(c.DispatchClusterMetricsPrefix),
   329  			clusterdispatch.Cache(cdcc),
   330  			clusterdispatch.RemoteDispatchTimeout(c.DispatchUpstreamTimeout),
   331  			clusterdispatch.ConcurrencyLimits(concurrencyLimits),
   332  		)
   333  		if err != nil {
   334  			return nil, fmt.Errorf("failed to configure cluster dispatch: %w", err)
   335  		}
   336  		closeables.AddWithError(cachingClusterDispatch.Close)
   337  	}
   338  
   339  	dispatchGrpcServer, err := c.DispatchServer.Complete(zerolog.InfoLevel,
   340  		func(server *grpc.Server) {
   341  			dispatchSvc.RegisterGrpcServices(server, cachingClusterDispatch)
   342  		},
   343  		grpc.ChainUnaryInterceptor(c.DispatchUnaryMiddleware...),
   344  		grpc.ChainStreamInterceptor(c.DispatchStreamingMiddleware...),
   345  		grpc.StatsHandler(otelgrpc.NewServerHandler()),
   346  	)
   347  	if err != nil {
   348  		return nil, fmt.Errorf("failed to create dispatch gRPC server: %w", err)
   349  	}
   350  	closeables.AddWithoutError(dispatchGrpcServer.GracefulStop)
   351  
   352  	datastoreFeatures, err := ds.Features(ctx)
   353  	if err != nil {
   354  		return nil, fmt.Errorf("error determining datastore features: %w", err)
   355  	}
   356  
   357  	v1SchemaServiceOption := services.V1SchemaServiceEnabled
   358  	if c.DisableV1SchemaAPI {
   359  		v1SchemaServiceOption = services.V1SchemaServiceDisabled
   360  	} else if c.V1SchemaAdditiveOnly {
   361  		v1SchemaServiceOption = services.V1SchemaServiceAdditiveOnly
   362  	}
   363  
   364  	watchServiceOption := services.WatchServiceEnabled
   365  	if !datastoreFeatures.Watch.Enabled {
   366  		log.Ctx(ctx).Warn().Str("reason", datastoreFeatures.Watch.Reason).Msg("watch api disabled; underlying datastore does not support it")
   367  		watchServiceOption = services.WatchServiceDisabled
   368  	}
   369  
   370  	opts := MiddlewareOption{
   371  		log.Logger,
   372  		c.GRPCAuthFunc,
   373  		!c.DisableVersionResponse,
   374  		dispatcher,
   375  		ds,
   376  		c.EnableRequestLogs,
   377  		c.EnableResponseLogs,
   378  		c.DisableGRPCLatencyHistogram,
   379  	}
   380  	defaultUnaryMiddlewareChain, err := DefaultUnaryMiddleware(opts)
   381  	if err != nil {
   382  		return nil, fmt.Errorf("error building default middlewares: %w", err)
   383  	}
   384  
   385  	defaultStreamingMiddlewareChain, err := DefaultStreamingMiddleware(opts)
   386  	if err != nil {
   387  		return nil, fmt.Errorf("error building default middlewares: %w", err)
   388  	}
   389  
   390  	sameMiddlewares := defaultUnaryMiddlewareChain.Names().Equal(defaultStreamingMiddlewareChain.Names())
   391  	if !sameMiddlewares {
   392  		return nil, fmt.Errorf("unary and streaming middlewares differ: %v / %v",
   393  			defaultUnaryMiddlewareChain.Names().AsSlice(),
   394  			defaultStreamingMiddlewareChain.Names().AsSlice(),
   395  		)
   396  	}
   397  
   398  	unaryMiddleware, err := c.buildUnaryMiddleware(defaultUnaryMiddlewareChain)
   399  	if err != nil {
   400  		return nil, fmt.Errorf("error building unary middlewares: %w", err)
   401  	}
   402  
   403  	streamingMiddleware, err := c.buildStreamingMiddleware(defaultStreamingMiddlewareChain)
   404  	if err != nil {
   405  		return nil, fmt.Errorf("error building streaming middlewares: %w", err)
   406  	}
   407  
   408  	permSysConfig := v1svc.PermissionsServerConfig{
   409  		MaxPreconditionsCount:           c.MaximumPreconditionCount,
   410  		MaxUpdatesPerWrite:              c.MaximumUpdatesPerWrite,
   411  		MaximumAPIDepth:                 c.DispatchMaxDepth,
   412  		MaxCaveatContextSize:            c.MaxCaveatContextSize,
   413  		MaxRelationshipContextSize:      c.MaxRelationshipContextSize,
   414  		MaxDatastoreReadPageSize:        c.MaxDatastoreReadPageSize,
   415  		StreamingAPITimeout:             c.StreamingAPITimeout,
   416  		MaxReadRelationshipsLimit:       c.MaxReadRelationshipsLimit,
   417  		MaxDeleteRelationshipsLimit:     c.MaxDeleteRelationshipsLimit,
   418  		MaxLookupResourcesLimit:         c.MaxLookupResourcesLimit,
   419  		MaxBulkExportRelationshipsLimit: c.MaxBulkExportRelationshipsLimit,
   420  	}
   421  
   422  	healthManager := health.NewHealthManager(dispatcher, ds)
   423  	grpcServer, err := c.GRPCServer.Complete(zerolog.InfoLevel,
   424  		func(server *grpc.Server) {
   425  			services.RegisterGrpcServices(
   426  				server,
   427  				healthManager,
   428  				dispatcher,
   429  				v1SchemaServiceOption,
   430  				watchServiceOption,
   431  				permSysConfig,
   432  				c.WatchHeartbeat,
   433  			)
   434  		},
   435  	)
   436  	if err != nil {
   437  		return nil, fmt.Errorf("failed to create gRPC server: %w", err)
   438  	}
   439  	closeables.AddWithoutError(grpcServer.GracefulStop)
   440  
   441  	gatewayServer, gatewayCloser, err := c.initializeGateway(ctx)
   442  	if err != nil {
   443  		return nil, err
   444  	}
   445  	closeables.AddCloser(gatewayCloser)
   446  	closeables.AddWithoutError(gatewayServer.Close)
   447  
   448  	var telemetryRegistry *prometheus.Registry
   449  
   450  	reporter := telemetry.DisabledReporter
   451  	if c.SilentlyDisableTelemetry {
   452  		reporter = telemetry.SilentlyDisabledReporter
   453  	} else if c.TelemetryEndpoint != "" && c.DatastoreConfig.DisableStats {
   454  		reporter = telemetry.DisabledReporter
   455  	} else if c.TelemetryEndpoint != "" {
   456  		log.Ctx(ctx).Debug().Msg("initializing telemetry collector")
   457  		registry, err := telemetry.RegisterTelemetryCollector(c.DatastoreConfig.Engine, ds)
   458  		if err != nil {
   459  			log.Warn().Err(err).Msg("unable to initialize telemetry collector")
   460  		} else {
   461  			telemetryRegistry = registry
   462  			reporter, err = telemetry.RemoteReporter(
   463  				telemetryRegistry, c.TelemetryEndpoint, c.TelemetryCAOverridePath, c.TelemetryInterval,
   464  			)
   465  			if err != nil {
   466  				log.Warn().Err(err).Msg("unable to initialize telemetry reporter")
   467  				telemetryRegistry = nil
   468  				reporter = telemetry.DisabledReporter
   469  			}
   470  		}
   471  	}
   472  
   473  	metricsServer, err := c.MetricsAPI.Complete(zerolog.InfoLevel, MetricsHandler(telemetryRegistry, c))
   474  	if err != nil {
   475  		return nil, fmt.Errorf("failed to initialize metrics server: %w", err)
   476  	}
   477  	closeables.AddWithoutError(metricsServer.Close)
   478  
   479  	return &completedServerConfig{
   480  		ds:                  ds,
   481  		gRPCServer:          grpcServer,
   482  		dispatchGRPCServer:  dispatchGrpcServer,
   483  		gatewayServer:       gatewayServer,
   484  		metricsServer:       metricsServer,
   485  		unaryMiddleware:     unaryMiddleware,
   486  		streamingMiddleware: streamingMiddleware,
   487  		presharedKeys:       c.PresharedSecureKey,
   488  		telemetryReporter:   reporter,
   489  		healthManager:       healthManager,
   490  		closeFunc:           closeables.Close,
   491  	}, nil
   492  }
   493  
   494  func (c *Config) buildUnaryMiddleware(defaultMiddleware *MiddlewareChain[grpc.UnaryServerInterceptor]) ([]grpc.UnaryServerInterceptor, error) {
   495  	chain := MiddlewareChain[grpc.UnaryServerInterceptor]{}
   496  	if defaultMiddleware != nil {
   497  		chain.chain = append(chain.chain, defaultMiddleware.chain...)
   498  	}
   499  
   500  	if err := chain.modify(c.UnaryMiddlewareModification...); err != nil {
   501  		return nil, err
   502  	}
   503  
   504  	return chain.ToGRPCInterceptors(), nil
   505  }
   506  
   507  func (c *Config) buildStreamingMiddleware(defaultMiddleware *MiddlewareChain[grpc.StreamServerInterceptor]) ([]grpc.StreamServerInterceptor, error) {
   508  	chain := MiddlewareChain[grpc.StreamServerInterceptor]{}
   509  	if defaultMiddleware != nil {
   510  		chain.chain = append(chain.chain, defaultMiddleware.chain...)
   511  	}
   512  
   513  	if err := chain.modify(c.StreamingMiddlewareModification...); err != nil {
   514  		return nil, err
   515  	}
   516  
   517  	return chain.ToGRPCInterceptors(), nil
   518  }
   519  
   520  // initializeGateway Configures the gateway to serve HTTP
   521  func (c *Config) initializeGateway(ctx context.Context) (util.RunnableHTTPServer, io.Closer, error) {
   522  	if len(c.HTTPGatewayUpstreamAddr) == 0 {
   523  		c.HTTPGatewayUpstreamAddr = c.GRPCServer.Address
   524  	} else {
   525  		log.Ctx(ctx).Info().Str("upstream", c.HTTPGatewayUpstreamAddr).Msg("Overriding REST gateway upstream")
   526  	}
   527  
   528  	if len(c.HTTPGatewayUpstreamTLSCertPath) == 0 {
   529  		c.HTTPGatewayUpstreamTLSCertPath = c.GRPCServer.TLSCertPath
   530  	} else {
   531  		log.Ctx(ctx).Info().Str("cert-path", c.HTTPGatewayUpstreamTLSCertPath).Msg("Overriding REST gateway upstream TLS")
   532  	}
   533  
   534  	// If the requested network is a buffered one, then disable the HTTPGateway.
   535  	if c.GRPCServer.Network == util.BufferedNetwork {
   536  		c.HTTPGateway.HTTPEnabled = false
   537  		gatewayServer, err := c.HTTPGateway.Complete(zerolog.InfoLevel, nil)
   538  		if err != nil {
   539  			return nil, nil, fmt.Errorf("failed skipping rest gateway initialization: %w", err)
   540  		}
   541  		return gatewayServer, nil, nil
   542  	}
   543  
   544  	var gatewayHandler http.Handler
   545  	closeableGatewayHandler, err := gateway.NewHandler(ctx, c.HTTPGatewayUpstreamAddr, c.HTTPGatewayUpstreamTLSCertPath)
   546  	if err != nil {
   547  		return nil, nil, fmt.Errorf("failed to initialize rest gateway: %w", err)
   548  	}
   549  	gatewayHandler = closeableGatewayHandler
   550  
   551  	if c.HTTPGatewayCorsEnabled {
   552  		log.Ctx(ctx).Info().Strs("origins", c.HTTPGatewayCorsAllowedOrigins).Msg("Setting REST gateway CORS policy")
   553  		gatewayHandler = cors.New(cors.Options{
   554  			AllowedOrigins:   c.HTTPGatewayCorsAllowedOrigins,
   555  			AllowCredentials: true,
   556  			AllowedHeaders:   []string{"Authorization", "Content-Type"},
   557  			Debug:            log.Debug().Enabled(),
   558  		}).Handler(gatewayHandler)
   559  	}
   560  
   561  	if c.HTTPGateway.HTTPEnabled {
   562  		log.Ctx(ctx).Info().Str("upstream", c.HTTPGatewayUpstreamAddr).Msg("starting REST gateway")
   563  	}
   564  
   565  	gatewayServer, err := c.HTTPGateway.Complete(zerolog.InfoLevel, gatewayHandler)
   566  	if err != nil {
   567  		return nil, nil, fmt.Errorf("failed to initialize rest gateway: %w", err)
   568  	}
   569  	return gatewayServer, closeableGatewayHandler, nil
   570  }
   571  
   572  // RunnableServer is a spicedb service set ready to run
   573  type RunnableServer interface {
   574  	Run(ctx context.Context) error
   575  	GRPCDialContext(ctx context.Context, opts ...grpc.DialOption) (*grpc.ClientConn, error)
   576  	DispatchNetDialContext(ctx context.Context, s string) (net.Conn, error)
   577  }
   578  
   579  // completedServerConfig holds the full configuration to run a spicedb server,
   580  // but is assumed have already been validated via `Complete()` on Config.
   581  // It offers limited options for mutation before Run() starts the services.
   582  type completedServerConfig struct {
   583  	ds datastore.Datastore
   584  
   585  	gRPCServer         util.RunnableGRPCServer
   586  	dispatchGRPCServer util.RunnableGRPCServer
   587  	gatewayServer      util.RunnableHTTPServer
   588  	metricsServer      util.RunnableHTTPServer
   589  	telemetryReporter  telemetry.Reporter
   590  	healthManager      health.Manager
   591  
   592  	unaryMiddleware     []grpc.UnaryServerInterceptor
   593  	streamingMiddleware []grpc.StreamServerInterceptor
   594  	presharedKeys       []string
   595  	closeFunc           func() error
   596  }
   597  
   598  func (c *completedServerConfig) GRPCDialContext(ctx context.Context, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
   599  	if len(c.presharedKeys) == 0 {
   600  		return c.gRPCServer.DialContext(ctx, opts...)
   601  	}
   602  	if c.gRPCServer.Insecure() {
   603  		opts = append(opts, grpcutil.WithInsecureBearerToken(c.presharedKeys[0]))
   604  	} else {
   605  		opts = append(opts, grpcutil.WithBearerToken(c.presharedKeys[0]))
   606  	}
   607  	return c.gRPCServer.DialContext(ctx, opts...)
   608  }
   609  
   610  func (c *completedServerConfig) DispatchNetDialContext(ctx context.Context, s string) (net.Conn, error) {
   611  	return c.dispatchGRPCServer.NetDialContext(ctx, s)
   612  }
   613  
   614  func (c *completedServerConfig) Run(ctx context.Context) error {
   615  	log.Ctx(ctx).Info().Type("datastore", c.ds).Msg("running server")
   616  	if startable := datastore.UnwrapAs[datastore.StartableDatastore](c.ds); startable != nil {
   617  		log.Ctx(ctx).Info().Msg("Start-ing datastore")
   618  		err := startable.Start(ctx)
   619  		if err != nil {
   620  			return err
   621  		}
   622  	}
   623  
   624  	g, ctx := errgroup.WithContext(ctx)
   625  
   626  	stopOnCancelWithErr := func(stopFn func() error) func() error {
   627  		return func() error {
   628  			<-ctx.Done()
   629  			return stopFn()
   630  		}
   631  	}
   632  
   633  	grpcServer := c.gRPCServer.WithOpts(
   634  		grpc.ChainUnaryInterceptor(c.unaryMiddleware...),
   635  		grpc.ChainStreamInterceptor(c.streamingMiddleware...),
   636  		grpc.StatsHandler(otelgrpc.NewServerHandler()))
   637  
   638  	g.Go(c.healthManager.Checker(ctx))
   639  	g.Go(grpcServer.Listen(ctx))
   640  	g.Go(c.dispatchGRPCServer.Listen(ctx))
   641  	g.Go(c.gatewayServer.ListenAndServe)
   642  	g.Go(c.metricsServer.ListenAndServe)
   643  	g.Go(func() error { return c.telemetryReporter(ctx) })
   644  
   645  	g.Go(stopOnCancelWithErr(c.closeFunc))
   646  
   647  	if err := g.Wait(); err != nil {
   648  		log.Ctx(ctx).Warn().Err(err).Msg("error shutting down server")
   649  		return err
   650  	}
   651  
   652  	return nil
   653  }