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 }