go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/appengine/main.go (about)

     1  // Copyright 2020 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 main
    16  
    17  import (
    18  	"context"
    19  	"net/http"
    20  	"time"
    21  
    22  	"golang.org/x/sync/errgroup"
    23  
    24  	"go.chromium.org/luci/config/server/cfgmodule"
    25  	"go.chromium.org/luci/grpc/prpc"
    26  	"go.chromium.org/luci/server"
    27  	"go.chromium.org/luci/server/analytics"
    28  	"go.chromium.org/luci/server/auth"
    29  	"go.chromium.org/luci/server/cron"
    30  	"go.chromium.org/luci/server/dsmapper"
    31  	"go.chromium.org/luci/server/encryptedcookies"
    32  	"go.chromium.org/luci/server/gaeemulation"
    33  	"go.chromium.org/luci/server/gerritauth"
    34  	"go.chromium.org/luci/server/module"
    35  	srvquota "go.chromium.org/luci/server/quota"
    36  	"go.chromium.org/luci/server/redisconn"
    37  	"go.chromium.org/luci/server/router"
    38  	"go.chromium.org/luci/server/secrets"
    39  	"go.chromium.org/luci/server/tq"
    40  	_ "go.chromium.org/luci/server/tq/txn/datastore"
    41  
    42  	// Using datastore for user sessions.
    43  	_ "go.chromium.org/luci/server/encryptedcookies/session/datastore"
    44  
    45  	// Ensure registration of validation rules before registering cfgcache.
    46  	_ "go.chromium.org/luci/cv/internal/configs/validation"
    47  
    48  	apiv0pb "go.chromium.org/luci/cv/api/v0"
    49  	"go.chromium.org/luci/cv/internal/buildbucket"
    50  	bbfacade "go.chromium.org/luci/cv/internal/buildbucket/facade"
    51  	bblistener "go.chromium.org/luci/cv/internal/buildbucket/listener"
    52  	"go.chromium.org/luci/cv/internal/changelist"
    53  	"go.chromium.org/luci/cv/internal/common"
    54  	"go.chromium.org/luci/cv/internal/common/bq"
    55  	"go.chromium.org/luci/cv/internal/common/tree"
    56  	"go.chromium.org/luci/cv/internal/configs/prjcfg/refresher"
    57  	"go.chromium.org/luci/cv/internal/configs/srvcfg"
    58  	"go.chromium.org/luci/cv/internal/gerrit"
    59  	gerritupdater "go.chromium.org/luci/cv/internal/gerrit/updater"
    60  	"go.chromium.org/luci/cv/internal/prjmanager"
    61  	pmimpl "go.chromium.org/luci/cv/internal/prjmanager/manager"
    62  	"go.chromium.org/luci/cv/internal/quota"
    63  	"go.chromium.org/luci/cv/internal/retention"
    64  	"go.chromium.org/luci/cv/internal/rpc/admin"
    65  	adminpb "go.chromium.org/luci/cv/internal/rpc/admin/api"
    66  	rpcv0 "go.chromium.org/luci/cv/internal/rpc/v0"
    67  	"go.chromium.org/luci/cv/internal/run"
    68  	runimpl "go.chromium.org/luci/cv/internal/run/impl"
    69  	"go.chromium.org/luci/cv/internal/run/rdb"
    70  	"go.chromium.org/luci/cv/internal/tryjob"
    71  	"go.chromium.org/luci/cv/internal/tryjob/tjcancel"
    72  	tjupdate "go.chromium.org/luci/cv/internal/tryjob/update"
    73  	"go.chromium.org/luci/cv/internal/userhtml"
    74  )
    75  
    76  func main() {
    77  	modules := []module.Module{
    78  		analytics.NewModuleFromFlags(),
    79  		cfgmodule.NewModuleFromFlags(),
    80  		cron.NewModuleFromFlags(),
    81  		dsmapper.NewModuleFromFlags(),
    82  		encryptedcookies.NewModuleFromFlags(),
    83  		gaeemulation.NewModuleFromFlags(),
    84  		gerritauth.NewModuleFromFlags(),
    85  		redisconn.NewModuleFromFlags(),
    86  		secrets.NewModuleFromFlags(),
    87  		tq.NewModuleFromFlags(),
    88  		srvquota.NewModuleFromFlags(),
    89  	}
    90  
    91  	server.Main(nil, modules, func(srv *server.Server) error {
    92  		env := common.MakeEnv(srv.Options)
    93  		gFactory, err := gerrit.NewFactory(
    94  			srv.Context,
    95  			// 3 US mirrors should suffice, effectively replicating a "quorum".
    96  			// These can be moved to to the service config if they have to be changed
    97  			// frequently.
    98  			"us1-mirror-", "us2-mirror-", "us3-mirror-",
    99  		)
   100  		if err != nil {
   101  			return err
   102  		}
   103  
   104  		// Register TQ handlers.
   105  		pmNotifier := prjmanager.NewNotifier(&tq.Default)
   106  		runNotifier := run.NewNotifier(&tq.Default)
   107  		tryjobNotifier := tryjob.NewNotifier(&tq.Default)
   108  		clMutator := changelist.NewMutator(&tq.Default, pmNotifier, runNotifier, tryjobNotifier)
   109  		clUpdater := changelist.NewUpdater(&tq.Default, clMutator)
   110  		gerritupdater.RegisterUpdater(clUpdater, gFactory)
   111  
   112  		bbFactory := buildbucket.NewClientFactory()
   113  		bbFacade := &bbfacade.Facade{
   114  			ClientFactory: bbFactory,
   115  		}
   116  
   117  		tryjobUpdater := tjupdate.NewUpdater(env, tryjobNotifier, runNotifier)
   118  		tryjobUpdater.RegisterBackend(bbFacade)
   119  		tryjobCancellator := tjcancel.NewCancellator(tryjobNotifier)
   120  		tryjobCancellator.RegisterBackend(bbFacade)
   121  
   122  		_ = pmimpl.New(pmNotifier, runNotifier, clMutator, gFactory, clUpdater)
   123  		tc, err := tree.NewClient(srv.Context)
   124  		if err != nil {
   125  			return err
   126  		}
   127  		bqc, err := bq.NewProdClient(srv.Context, srv.Options.CloudProject)
   128  		if err != nil {
   129  			return err
   130  		}
   131  		rdbClientFactory := rdb.NewRecorderClientFactory()
   132  		qm := quota.NewManager()
   133  		_ = runimpl.New(runNotifier, pmNotifier, tryjobNotifier, clMutator, clUpdater, gFactory, bbFactory, tc, bqc, rdbClientFactory, qm, env)
   134  
   135  		// Setup pRPC authentication.
   136  		srv.SetRPCAuthMethods([]auth.Method{
   137  			// The default method used by majority of clients.
   138  			&auth.GoogleOAuth2Method{
   139  				Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
   140  			},
   141  			// For authenticating calls from Gerrit plugins.
   142  			&gerritauth.Method,
   143  		})
   144  		srv.ConfigurePRPC(func(p *prpc.Server) {
   145  			p.AccessControl = func(context.Context, string) prpc.AccessControlDecision {
   146  				return prpc.AccessControlDecision{
   147  					AllowCrossOriginRequests: true,
   148  					AllowCredentials:         true,
   149  					AllowHeaders:             []string{gerritauth.Method.Header},
   150  				}
   151  			}
   152  		})
   153  
   154  		adminpb.RegisterAdminServer(srv, admin.New(
   155  			&tq.Default, &dsmapper.Default,
   156  			clUpdater, pmNotifier, runNotifier,
   157  		))
   158  		apiv0pb.RegisterRunsServer(srv, &rpcv0.RunsServer{})
   159  		apiv0pb.RegisterGerritIntegrationServer(srv, &rpcv0.GerritIntegrationServer{})
   160  
   161  		// Register cron.
   162  		pcr := refresher.NewRefresher(&tq.Default, pmNotifier, qm, env)
   163  		cron.RegisterHandler("refresh-config", func(ctx context.Context) error {
   164  			return refreshConfig(ctx, pcr)
   165  		})
   166  		kickNewListenersFn := bblistener.Register(&tq.Default, srv.Options.CloudProject, tryjobNotifier, tryjobUpdater)
   167  		cron.RegisterHandler("kick-bb-pubsub-listeners", func(ctx context.Context) error {
   168  			return kickNewListenersFn(ctx)
   169  		})
   170  		retention.RegisterCrons(&tq.Default, runNotifier)
   171  
   172  		// The service has no general-use UI, so just redirect to the RPC Explorer.
   173  		srv.Routes.GET("/", nil, func(c *router.Context) {
   174  			http.Redirect(c.Writer, c.Request, "/rpcexplorer/", http.StatusFound)
   175  		})
   176  
   177  		userhtml.InstallHandlers(srv)
   178  
   179  		// When running locally, serve static files ourselves as well.
   180  		if !srv.Options.Prod {
   181  			srv.Routes.Static("/static", nil, http.Dir("./static"))
   182  		}
   183  		return nil
   184  	})
   185  }
   186  
   187  func refreshConfig(ctx context.Context, pcr *refresher.Refresher) error {
   188  	// The cron job interval is 1 minute.
   189  	ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
   190  	defer cancel()
   191  	eg, ctx := errgroup.WithContext(ctx)
   192  	eg.Go(func() error { return srvcfg.ImportConfig(ctx) })
   193  	eg.Go(func() error { return pcr.SubmitRefreshTasks(ctx) })
   194  	return eg.Wait()
   195  }