| /* |
| Automatic DEPS rolls of Skia into Chrome. |
| */ |
| |
| package main |
| |
| import ( |
| "context" |
| "encoding/json" |
| "flag" |
| "fmt" |
| "net/http" |
| "net/url" |
| "os/user" |
| "path" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "text/template" |
| "time" |
| |
| "github.com/gorilla/mux" |
| "go.skia.org/infra/go/depot_tools" |
| "go.skia.org/infra/go/metadata" |
| "go.skia.org/infra/go/sklog" |
| |
| "go.skia.org/infra/autoroll/go/autorollerv2" |
| "go.skia.org/infra/autoroll/go/repo_manager" |
| "go.skia.org/infra/go/common" |
| "go.skia.org/infra/go/gerrit" |
| "go.skia.org/infra/go/httputils" |
| "go.skia.org/infra/go/login" |
| "go.skia.org/infra/go/metrics2" |
| "go.skia.org/infra/go/skiaversion" |
| "go.skia.org/infra/go/util" |
| ) |
| |
| var ( |
| arb *autorollerv2.AutoRoller = nil |
| |
| mainTemplate *template.Template = nil |
| ) |
| |
| // flags |
| var ( |
| childName = flag.String("childName", "Skia", "Name of the project to roll.") |
| childPath = flag.String("childPath", "src/third_party/skia", "Path within parent repo of the project to roll.") |
| childBranch = flag.String("child_branch", "master", "Branch of the project we want to roll.") |
| cqExtraTrybots = flag.String("cqExtraTrybots", "", "Comma-separated list of trybots to run.") |
| host = flag.String("host", "localhost", "HTTP service host") |
| local = flag.Bool("local", false, "Running locally if true. As opposed to in production.") |
| parentRepo = flag.String("parent_repo", common.REPO_CHROMIUM, "Repo to roll into.") |
| parentBranch = flag.String("parent_branch", "master", "Branch of the parent repo we want to roll into.") |
| port = flag.String("port", ":8000", "HTTP service port (e.g., ':8000')") |
| promPort = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')") |
| resourcesDir = flag.String("resources_dir", "", "The directory to find templates, JS, and CSS files. If blank the current directory will be used.") |
| sheriff = flag.String("sheriff", "", "Email address to CC on rolls, or URL from which to obtain such an email address.") |
| strategy = flag.String("strategy", repo_manager.ROLL_STRATEGY_BATCH, "DEPS roll strategy; how many commits should be rolled at once.") |
| useMetadata = flag.Bool("use_metadata", true, "Load sensitive values from metadata not from flags.") |
| workdir = flag.String("workdir", ".", "Directory to use for scratch work.") |
| rollIntoAndroid = flag.Bool("roll_into_android", false, "Roll into Android; do not do a DEPS/Manifest roll.") |
| useManifest = flag.Bool("use_manifest", false, "Do a Manifest roll.") |
| gerritUrl = flag.String("gerrit_url", gerrit.GERRIT_CHROMIUM_URL, "Gerrit URL the roller will be uploading issues to.") |
| ) |
| |
| func getSheriff() ([]string, error) { |
| emails, err := getSheriffHelper() |
| if err != nil { |
| return nil, err |
| } |
| if strings.Contains(*parentRepo, "chromium") { |
| for i, s := range emails { |
| emails[i] = strings.Replace(s, "google.com", "chromium.org", 1) |
| } |
| } |
| return emails, nil |
| } |
| |
| func getSheriffHelper() ([]string, error) { |
| // If the passed-in sheriff doesn't look like a URL, it's probably an |
| // email address. Use it directly. |
| if _, err := url.ParseRequestURI(*sheriff); err != nil { |
| if strings.Count(*sheriff, "@") == 1 { |
| return []string{*sheriff}, nil |
| } else { |
| return nil, fmt.Errorf("Sheriff must be an email address or a valid URL; %q doesn't look like either.", *sheriff) |
| } |
| } |
| |
| // Hit the URL to get the email address. Expect JSON. |
| client := httputils.NewTimeoutClient() |
| resp, err := client.Get(*sheriff) |
| if err != nil { |
| return nil, err |
| } |
| defer util.Close(resp.Body) |
| var sheriff struct { |
| Username string `json:"username"` |
| } |
| if err := json.NewDecoder(resp.Body).Decode(&sheriff); err != nil { |
| return nil, err |
| } |
| return []string{sheriff.Username}, nil |
| } |
| |
| func getCQExtraTrybots() string { |
| return *cqExtraTrybots |
| } |
| |
| func reloadTemplates() { |
| // Change the current working directory to two directories up from this source file so that we |
| // can read templates and serve static (res/) files. |
| |
| if *resourcesDir == "" { |
| _, filename, _, _ := runtime.Caller(0) |
| *resourcesDir = filepath.Join(filepath.Dir(filename), "../..") |
| } |
| mainTemplate = template.Must(template.ParseFiles( |
| filepath.Join(*resourcesDir, "templates/main.html"), |
| filepath.Join(*resourcesDir, "templates/header.html"), |
| )) |
| } |
| |
| func Init() { |
| reloadTemplates() |
| } |
| |
| func modeJsonHandler(w http.ResponseWriter, r *http.Request) { |
| if !login.IsGoogler(r) { |
| httputils.ReportError(w, r, fmt.Errorf("User does not have edit rights."), "You must be logged in with an @google.com account to do that.") |
| return |
| } |
| |
| var mode struct { |
| Message string `json:"message"` |
| Mode string `json:"mode"` |
| } |
| defer util.Close(r.Body) |
| if err := json.NewDecoder(r.Body).Decode(&mode); err != nil { |
| httputils.ReportError(w, r, err, "Failed to decode request body.") |
| return |
| } |
| |
| if err := arb.SetMode(mode.Mode, login.LoggedInAs(r), mode.Message); err != nil { |
| httputils.ReportError(w, r, err, "Failed to set AutoRoll mode.") |
| return |
| } |
| |
| // Return the ARB status. |
| statusJsonHandler(w, r) |
| } |
| |
| func statusJsonHandler(w http.ResponseWriter, r *http.Request) { |
| w.Header().Set("Content-Type", "application/json") |
| // Obtain the status info. Only display error messages if the user |
| // is a logged-in Googler. |
| status := arb.GetStatus(login.IsGoogler(r)) |
| if err := json.NewEncoder(w).Encode(&status); err != nil { |
| sklog.Error(err) |
| } |
| } |
| |
| func mainHandler(w http.ResponseWriter, r *http.Request) { |
| w.Header().Set("Content-Type", "text/html") |
| |
| // Don't use cached templates in testing mode. |
| if *local { |
| reloadTemplates() |
| } |
| mainPage := struct { |
| ProjectName string |
| ProjectUser string |
| }{ |
| ProjectName: *childName, |
| ProjectUser: arb.GetUser(), |
| } |
| if err := mainTemplate.Execute(w, mainPage); err != nil { |
| sklog.Errorln("Failed to expand template:", err) |
| } |
| } |
| |
| func runServer() { |
| r := mux.NewRouter() |
| r.PathPrefix("/res/").HandlerFunc(httputils.MakeResourceHandler(*resourcesDir)) |
| r.HandleFunc("/", mainHandler) |
| r.HandleFunc("/json/mode", modeJsonHandler).Methods("POST") |
| r.HandleFunc("/json/status", httputils.CorsHandler(statusJsonHandler)) |
| r.HandleFunc("/json/version", skiaversion.JsonHandler) |
| r.HandleFunc("/oauth2callback/", login.OAuth2CallbackHandler) |
| r.HandleFunc("/logout/", login.LogoutHandler) |
| r.HandleFunc("/loginstatus/", login.StatusHandler) |
| http.Handle("/", httputils.LoggingGzipRequestResponse(r)) |
| sklog.Fatal(http.ListenAndServe(*port, nil)) |
| } |
| |
| func main() { |
| common.InitWithMust( |
| "autoroll", |
| common.PrometheusOpt(promPort), |
| common.CloudLoggingOpt(), |
| ) |
| |
| Init() |
| |
| v, err := skiaversion.GetVersion() |
| if err != nil { |
| sklog.Fatal(err) |
| } |
| sklog.Infof("Version %s, built at %s", v.Commit, v.Date) |
| |
| if *local { |
| *useMetadata = false |
| } |
| |
| user, err := user.Current() |
| if err != nil { |
| sklog.Fatal(err) |
| } |
| gitcookiesPath := path.Join(user.HomeDir, ".gitcookies") |
| androidInternalGerritUrl := *gerritUrl |
| |
| if *useMetadata { |
| // If we are rolling into Android get the Gerrit Url from metadata. |
| androidInternalGerritUrl, err = metadata.ProjectGet("android_internal_gerrit_url") |
| if err != nil { |
| sklog.Fatal(err) |
| } |
| } |
| |
| // Create the code review API client. |
| gUrl := *gerritUrl |
| if *rollIntoAndroid { |
| if !*local { |
| // Android roller uses the gitcookie created by gcompute-tools/git-cookie-authdaemon. |
| // TODO(rmistry): Turn this on via the GCE setup script so that it exists right when the instance comes up? |
| gitcookiesPath = filepath.Join(user.HomeDir, ".git-credential-cache", "cookie") |
| } |
| gUrl = androidInternalGerritUrl |
| } else { |
| if strings.Contains(*parentRepo, "skia") { |
| gUrl = gerrit.GERRIT_SKIA_URL |
| } |
| } |
| g, err := gerrit.NewGerrit(gUrl, gitcookiesPath, nil) |
| if err != nil { |
| sklog.Fatalf("Failed to create Gerrit client: %s", err) |
| } |
| g.TurnOnAuthenticatedGets() |
| |
| // Retrieve the list of extra CQ trybots. |
| // TODO(borenet): Make this editable on the web front-end. |
| cqExtraTrybots := getCQExtraTrybots() |
| sklog.Infof("CQ extra trybots: %s", cqExtraTrybots) |
| |
| // Retrieve the initial email list. |
| emails, err := getSheriff() |
| if err != nil { |
| sklog.Fatal(err) |
| } |
| sklog.Infof("Sheriff: %s", strings.Join(emails, ", ")) |
| |
| // Sync depot_tools. |
| var depotTools string |
| if !*rollIntoAndroid { |
| depotTools, err = depot_tools.Sync(*workdir) |
| if err != nil { |
| sklog.Fatal(err) |
| } |
| } |
| |
| // Start the autoroller. |
| if *rollIntoAndroid { |
| arb, err = autorollerv2.NewAndroidAutoRoller(*workdir, *parentBranch, *childPath, *childBranch, cqExtraTrybots, emails, g, *strategy) |
| } else if *useManifest { |
| arb, err = autorollerv2.NewManifestAutoRoller(*workdir, *parentRepo, *parentBranch, *childPath, *childBranch, cqExtraTrybots, emails, g, depotTools, *strategy) |
| } else { |
| arb, err = autorollerv2.NewDEPSAutoRoller(*workdir, *parentRepo, *parentBranch, *childPath, *childBranch, cqExtraTrybots, emails, g, depotTools, *strategy) |
| } |
| if err != nil { |
| sklog.Fatal(err) |
| } |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| arb.Start(time.Minute /* tickFrequency */, 15*time.Minute /* repoFrequency */, ctx) |
| |
| // Feed AutoRoll stats into metrics. |
| go func() { |
| for range time.Tick(time.Minute) { |
| status := arb.GetStatus(false) |
| v := int64(0) |
| if status.LastRoll != nil && status.LastRoll.Closed && status.LastRoll.Committed { |
| v = int64(1) |
| } |
| metrics2.GetInt64Metric("autoroll.last-roll-result", map[string]string{"child-path": *childPath}).Update(v) |
| } |
| }() |
| |
| // Update the current sheriff in a loop. |
| go func() { |
| for range time.Tick(30 * time.Minute) { |
| emails, err := getSheriff() |
| if err != nil { |
| sklog.Errorf("Failed to retrieve current sheriff: %s", err) |
| } else { |
| arb.SetEmails(emails) |
| } |
| } |
| }() |
| |
| login.SimpleInitMust(*port, *local) |
| |
| runServer() |
| } |