go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/quotabeta/module.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package quota
    16  
    17  import (
    18  	"context"
    19  	"flag"
    20  
    21  	"go.chromium.org/luci/server/cron"
    22  	"go.chromium.org/luci/server/module"
    23  	pb "go.chromium.org/luci/server/quotabeta/proto"
    24  	"go.chromium.org/luci/server/redisconn"
    25  )
    26  
    27  // ModuleName is the globally-unique name for this module.
    28  // Useful for registering this module as a dependency of other modules.
    29  var ModuleName = module.RegisterName("go.chromium.org/luci/server/quota")
    30  
    31  // Ensure quotaModule implements server.Module at compile-time.
    32  var _ module.Module = &quotaModule{}
    33  
    34  // quotaModule implements module.Module.
    35  type quotaModule struct {
    36  	opts *ModuleOptions
    37  }
    38  
    39  // Dependencies returns required and optional dependencies for this module.
    40  // Implements module.Module.
    41  func (*quotaModule) Dependencies() []module.Dependency {
    42  	return []module.Dependency{
    43  		module.OptionalDependency(cron.ModuleName),
    44  		module.RequiredDependency(redisconn.ModuleName),
    45  	}
    46  }
    47  
    48  // Initialize initializes this module by ensuring a quotaconfig.Interface
    49  // implementation is available in the serving context, and installing cron
    50  // routes if configured (see ModuleOptions).
    51  // Implements module.Module.
    52  func (m *quotaModule) Initialize(ctx context.Context, host module.Host, opts module.HostOptions) (context.Context, error) {
    53  	// server.Main calls Initialize before calling the user-provided callback where
    54  	// users of the quota library are likely to provide a quotaconfig.Interface.
    55  	// Use a warmup callback, which is called after the user-provided callback has
    56  	// been executed, to run initialization logic requiring quotaconfig.Interface.
    57  	host.RegisterWarmup(func(ctx context.Context) {
    58  		// getInterface panics if a quotaconfig.Interface isn't available (see Use).
    59  		// Intentionally force a panic if one isn't available.
    60  		cfg := getInterface(ctx)
    61  
    62  		// Register the cron handler if specified.
    63  		if m.opts.ConfigRefreshCronHandlerID != "" {
    64  			cron.RegisterHandler(m.opts.ConfigRefreshCronHandlerID, func(ctx context.Context) error {
    65  				// Fetch the quotaconfig.Interface from the context each time
    66  				// in case the implementation being used changes later on.
    67  				return getInterface(ctx).Refresh(ctx)
    68  			})
    69  		}
    70  
    71  		// Attempt to call Refresh so policy configs are available before serving.
    72  		// This is best-effort, users should ensure they're calling Refresh regularly
    73  		// (either manually, or by setting m.opts.ConfigRefreshCronHandlerID).
    74  		_ = cfg.Refresh(ctx)
    75  	})
    76  
    77  	if m.opts.AdminServiceReaders != "" && m.opts.AdminServiceWriters != "" {
    78  		pb.RegisterQuotaAdminServer(host, NewQuotaAdminServer(m.opts.AdminServiceReaders, m.opts.AdminServiceWriters))
    79  	}
    80  	return ctx, nil
    81  }
    82  
    83  // Name returns the module.Name for this module.
    84  // Implements module.Module.
    85  func (*quotaModule) Name() module.Name {
    86  	return ModuleName
    87  }
    88  
    89  // ModuleOptions is a set of configuration options for the quota module.
    90  type ModuleOptions struct {
    91  	// AdminServerReaders is a Chrome Infra Auth group authorized to use read-only
    92  	// methods of the quota admin pRPC service. If unspecified, the service will
    93  	// not be exposed.
    94  	AdminServiceReaders string
    95  
    96  	// AdminServerWriters is a Chrome Infra Auth group authorized to use all
    97  	// methods of the quota admin pRPC service. If unspecified, the service will
    98  	// not be exposed.
    99  	AdminServiceWriters string
   100  
   101  	// ConfigRefreshCronHandlerID is the ID for this module's config refresh
   102  	// handler. If specified, the module ensures Refresh is called on the
   103  	// quotaconfig.Interface in the server context. The handler will be installed
   104  	// at <serving-prefix>/<ID>. The serving prefix is controlled by server/cron.
   105  	// If unspecified, module users should refresh policy configs periodically by
   106  	// manually calling Refresh.
   107  	ConfigRefreshCronHandlerID string
   108  }
   109  
   110  // Register adds command line flags for these module options to the given
   111  // *flag.FlagSet. Mutates module options by initializing defaults.
   112  func (o *ModuleOptions) Register(f *flag.FlagSet) {
   113  	f.StringVar(&o.AdminServiceReaders, "quota-admin-service-readers", "", "Chrome Infra Auth group authorized to use read-only admin methods.")
   114  	f.StringVar(&o.AdminServiceWriters, "quota-admin-service-writers", "", "Chrome Infra Auth group authorized to use all admin methods.")
   115  	f.StringVar(&o.ConfigRefreshCronHandlerID, "quota-cron-handler-id", "", "Config refresh handler ID.")
   116  }
   117  
   118  // NewModule returns a module.Module for the quota library initialized from the
   119  // given *ModuleOptions.
   120  func NewModule(opts *ModuleOptions) module.Module {
   121  	return &quotaModule{
   122  		opts: opts,
   123  	}
   124  }
   125  
   126  // NewModuleFromFlags returns a module.Module for the quota library which can be
   127  // initialized from command line flags.
   128  func NewModuleFromFlags() module.Module {
   129  	opts := &ModuleOptions{}
   130  	opts.Register(flag.CommandLine)
   131  	return NewModule(opts)
   132  }