blob: 29be1c3e8a0fb5f992f7aa94a00eb63bb3c443bf [file] [log] [blame]
// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"flag"
"net/http"
"time"
"golang.org/x/sync/errgroup"
"go.chromium.org/luci/config/server/cfgmodule"
"go.chromium.org/luci/grpc/prpc"
"go.chromium.org/luci/server"
"go.chromium.org/luci/server/analytics"
"go.chromium.org/luci/server/auth"
"go.chromium.org/luci/server/cron"
"go.chromium.org/luci/server/dsmapper"
"go.chromium.org/luci/server/encryptedcookies"
"go.chromium.org/luci/server/gaeemulation"
"go.chromium.org/luci/server/gerritauth"
"go.chromium.org/luci/server/module"
srvquota "go.chromium.org/luci/server/quota"
"go.chromium.org/luci/server/redisconn"
"go.chromium.org/luci/server/router"
"go.chromium.org/luci/server/secrets"
"go.chromium.org/luci/server/tq"
_ "go.chromium.org/luci/server/tq/txn/datastore"
// Using datastore for user sessions.
_ "go.chromium.org/luci/server/encryptedcookies/session/datastore"
// Ensure registration of validation rules before registering cfgcache.
_ "go.chromium.org/luci/cv/internal/configs/validation"
apiv0pb "go.chromium.org/luci/cv/api/v0"
"go.chromium.org/luci/cv/internal/buildbucket"
bbfacade "go.chromium.org/luci/cv/internal/buildbucket/facade"
bblistener "go.chromium.org/luci/cv/internal/buildbucket/listener"
"go.chromium.org/luci/cv/internal/changelist"
"go.chromium.org/luci/cv/internal/common"
"go.chromium.org/luci/cv/internal/common/bq"
"go.chromium.org/luci/cv/internal/common/tree"
"go.chromium.org/luci/cv/internal/configs/prjcfg/refresher"
"go.chromium.org/luci/cv/internal/configs/srvcfg"
"go.chromium.org/luci/cv/internal/gerrit"
gerritupdater "go.chromium.org/luci/cv/internal/gerrit/updater"
"go.chromium.org/luci/cv/internal/prjmanager"
pmimpl "go.chromium.org/luci/cv/internal/prjmanager/manager"
"go.chromium.org/luci/cv/internal/quota"
"go.chromium.org/luci/cv/internal/retention"
"go.chromium.org/luci/cv/internal/rpc/admin"
adminpb "go.chromium.org/luci/cv/internal/rpc/admin/api"
rpcv0 "go.chromium.org/luci/cv/internal/rpc/v0"
"go.chromium.org/luci/cv/internal/run"
runimpl "go.chromium.org/luci/cv/internal/run/impl"
"go.chromium.org/luci/cv/internal/run/rdb"
"go.chromium.org/luci/cv/internal/tryjob"
"go.chromium.org/luci/cv/internal/tryjob/tjcancel"
tjupdate "go.chromium.org/luci/cv/internal/tryjob/update"
"go.chromium.org/luci/cv/internal/userhtml"
)
func main() {
modules := []module.Module{
analytics.NewModuleFromFlags(),
cfgmodule.NewModuleFromFlags(),
cron.NewModuleFromFlags(),
dsmapper.NewModuleFromFlags(),
encryptedcookies.NewModuleFromFlags(),
gaeemulation.NewModuleFromFlags(),
gerritauth.NewModuleFromFlags(),
redisconn.NewModuleFromFlags(),
secrets.NewModuleFromFlags(),
tq.NewModuleFromFlags(),
srvquota.NewModuleFromFlags(),
}
luciTreeStatusHost := "staging.treestatus.api.luci.app"
flag.StringVar(&luciTreeStatusHost, "luci-tree-status-host", luciTreeStatusHost, "Host for luci-tree-status.")
server.Main(nil, modules, func(srv *server.Server) error {
env := common.MakeEnv(srv.Options)
gFactory, err := gerrit.NewFactory(
srv.Context,
// 3 US mirrors should suffice, effectively replicating a "quorum".
// These can be moved to to the service config if they have to be changed
// frequently.
"us1-mirror-", "us2-mirror-", "us3-mirror-",
)
if err != nil {
return err
}
// Register TQ handlers.
pmNotifier := prjmanager.NewNotifier(&tq.Default)
runNotifier := run.NewNotifier(&tq.Default)
tryjobNotifier := tryjob.NewNotifier(&tq.Default)
clMutator := changelist.NewMutator(&tq.Default, pmNotifier, runNotifier, tryjobNotifier)
clUpdater := changelist.NewUpdater(&tq.Default, clMutator)
gerritupdater.RegisterUpdater(clUpdater, gFactory)
bbFactory := buildbucket.NewClientFactory()
bbFacade := &bbfacade.Facade{
ClientFactory: bbFactory,
}
tryjobUpdater := tjupdate.NewUpdater(env, tryjobNotifier, runNotifier)
tryjobUpdater.RegisterBackend(bbFacade)
tryjobCancellator := tjcancel.NewCancellator(tryjobNotifier)
tryjobCancellator.RegisterBackend(bbFacade)
_ = pmimpl.New(pmNotifier, runNotifier, clMutator, gFactory, clUpdater)
tc, err := tree.NewClient(srv.Context, luciTreeStatusHost)
if err != nil {
return err
}
bqc, err := bq.NewProdClient(srv.Context, srv.Options.CloudProject)
if err != nil {
return err
}
rdbClientFactory := rdb.NewRecorderClientFactory()
qm := quota.NewManager(gFactory)
_ = runimpl.New(runNotifier, pmNotifier, tryjobNotifier, clMutator, clUpdater, gFactory, bbFactory, tc, bqc, rdbClientFactory, qm, env)
// Setup pRPC authentication.
srv.SetRPCAuthMethods([]auth.Method{
// The default method used by majority of clients.
&auth.GoogleOAuth2Method{
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
},
// For authenticating calls from Gerrit plugins.
&gerritauth.Method,
})
srv.ConfigurePRPC(func(p *prpc.Server) {
p.AccessControl = func(context.Context, string) prpc.AccessControlDecision {
return prpc.AccessControlDecision{
AllowCrossOriginRequests: true,
AllowCredentials: true,
AllowHeaders: []string{gerritauth.Method.Header},
}
}
})
adminpb.RegisterAdminServer(srv, admin.New(
&tq.Default, &dsmapper.Default,
clUpdater, pmNotifier, runNotifier,
))
apiv0pb.RegisterRunsServer(srv, &rpcv0.RunsServer{})
apiv0pb.RegisterGerritIntegrationServer(srv, &rpcv0.GerritIntegrationServer{})
// Register cron.
pcr := refresher.NewRefresher(&tq.Default, pmNotifier, qm, env)
cron.RegisterHandler("refresh-config", func(ctx context.Context) error {
return refreshConfig(ctx, pcr)
})
kickNewListenersFn := bblistener.Register(&tq.Default, srv.Options.CloudProject, tryjobNotifier, tryjobUpdater)
cron.RegisterHandler("kick-bb-pubsub-listeners", func(ctx context.Context) error {
return kickNewListenersFn(ctx)
})
retention.RegisterCrons(&tq.Default, runNotifier)
// The service has no general-use UI, so just redirect to the RPC Explorer.
srv.Routes.GET("/", nil, func(c *router.Context) {
http.Redirect(c.Writer, c.Request, "/rpcexplorer/", http.StatusFound)
})
userhtml.InstallHandlers(srv)
// When running locally, serve static files ourselves as well.
if !srv.Options.Prod {
srv.Routes.Static("/static", nil, http.Dir("./static"))
}
return nil
})
}
func refreshConfig(ctx context.Context, pcr *refresher.Refresher) error {
// The cron job interval is 1 minute.
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error { return srvcfg.ImportConfig(ctx) })
eg.Go(func() error { return pcr.SubmitRefreshTasks(ctx) })
return eg.Wait()
}