diff --git a/prow/cmd/deck/BUILD.bazel b/prow/cmd/deck/BUILD.bazel index 3e0f061cc98d3..2e629331a30e1 100644 --- a/prow/cmd/deck/BUILD.bazel +++ b/prow/cmd/deck/BUILD.bazel @@ -105,6 +105,7 @@ go_library( "//prow/errorutil:go_default_library", "//prow/flagutil:go_default_library", "//prow/gcsupload:go_default_library", + "//prow/github:go_default_library", "//prow/githuboauth:go_default_library", "//prow/kube:go_default_library", "//prow/logrusutil:go_default_library", diff --git a/prow/cmd/deck/main.go b/prow/cmd/deck/main.go index 4ef665a6acf01..39a07d1e96db8 100644 --- a/prow/cmd/deck/main.go +++ b/prow/cmd/deck/main.go @@ -54,6 +54,7 @@ import ( "k8s.io/test-infra/prow/config" "k8s.io/test-infra/prow/deck/jobs" prowflagutil "k8s.io/test-infra/prow/flagutil" + prowgithub "k8s.io/test-infra/prow/github" "k8s.io/test-infra/prow/githuboauth" "k8s.io/test-infra/prow/kube" "k8s.io/test-infra/prow/logrusutil" @@ -1151,20 +1152,34 @@ func handleProwJob(prowJobClient prowv1.ProwJobInterface) http.HandlerFunc { } // canTriggerJob determines whether the given user can trigger any job. -func canTriggerJob(user string, cfg config.Getter) bool { - if cfg().Deck.RerunAuthConfig.AllowAnyone { +func canTriggerJob(user string, cfg config.RerunAuthConfig) bool { + if cfg.AllowAnyone { return true } - if cfg().Deck.RerunAuthConfig.AuthorizedUsers != nil { - for _, s := range cfg().Deck.RerunAuthConfig.AuthorizedUsers { - if user == s { - return true - } + for _, s := range cfg.AuthorizedUsers { + if prowgithub.NormLogin(user) == prowgithub.NormLogin(s) { + return true } } return false } +func marshalJob(w http.ResponseWriter, pj prowapi.ProwJob) { + b, err := yaml.Marshal(&pj) + if err != nil { + http.Error(w, fmt.Sprintf("Error marshaling: %v", err), http.StatusInternalServerError) + logrus.WithError(err).Error("Error marshaling job.") + return + } + w.Header().Set("Content-Type", "application/json") + if _, err := w.Write(b); err != nil { + logrus.WithError(err).Error("Error writing log.") + } +} + +// handleRerun triggers a rerun of the given job if that features is enabled, it receives a +// POST request, and the user has the necessary permissions. Otherwise, it writes the config +// for a new job but does not trigger it. func handleRerun(prowJobClient prowv1.ProwJobInterface, createProwJob bool, cfg config.Getter) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("prowjob") @@ -1187,41 +1202,33 @@ func handleRerun(prowJobClient prowv1.ProwJobInterface, createProwJob bool, cfg // On Prow instances that require auth even for viewing Deck this is okayish, because the Prowjob UUID // is hard to guess // Ref: https://github.com/kubernetes/test-infra/pull/12827#issuecomment-502850414 - if createProwJob { - if r.Method != http.MethodPost { - http.Error(w, "request must be of type POST", http.StatusMethodNotAllowed) + if r.Method == http.MethodPost { + if !createProwJob { + http.Error(w, "Direct rerun feature is not yet enabled", http.StatusMethodNotAllowed) return } - githubCookie, err := r.Cookie("github_login") + login, err := r.Cookie("github_login") if err == http.ErrNoCookie { - http.Redirect(w, r, "/github-login", http.StatusFound) + http.Redirect(w, r, "/github-login?dest=%2F?rerun=work_in_progress", http.StatusFound) + return } else if err != nil { http.Error(w, fmt.Sprintf("Error retrieving GitHub cookie: %v", err), http.StatusInternalServerError) + logrus.WithError(err).Errorf("Error retrieving GitHub cookie") return } - if canTriggerJob(githubCookie.Value, cfg) { - if _, err := prowJobClient.Create(&newPJ); err != nil { - logrus.WithError(err).Error("Error creating job") - http.Error(w, fmt.Sprintf("Error creating job: %v", err), http.StatusInternalServerError) - return - } - http.Redirect(w, r, "/?rerun=success", http.StatusFound) + if !canTriggerJob(login.Value, cfg().Deck.RerunAuthConfig) { + http.Redirect(w, r, "/?rerun=error", http.StatusFound) return - } else { - http.Redirect(w, r, "/?rerun=denied", http.StatusFound) + } + if _, err := prowJobClient.Create(&newPJ); err != nil { + logrus.WithError(err).Error("Error creating job") + http.Error(w, fmt.Sprintf("Error creating job: %v", err), http.StatusInternalServerError) return } - } - b, err := yaml.Marshal(&newPJ) - if err != nil { - http.Error(w, fmt.Sprintf("Error marshaling: %v", err), http.StatusInternalServerError) - logrus.WithError(err).Error("Error marshaling jobs.") + http.Redirect(w, r, "/?rerun=success", http.StatusFound) return } - w.Header().Set("Content-Type", "application/json") - if _, err := w.Write(b); err != nil { - logrus.WithError(err).Error("Error writing log.") - } + marshalJob(w, newPJ) } } diff --git a/prow/cmd/deck/main_test.go b/prow/cmd/deck/main_test.go index 626590ae0de95..44c523caeccc1 100644 --- a/prow/cmd/deck/main_test.go +++ b/prow/cmd/deck/main_test.go @@ -263,6 +263,7 @@ func TestRerun(t *testing.T) { rerunCreatesJob bool shouldCreateProwJob bool httpCode int + httpMethod string }{ { name: "Handler returns ProwJob", @@ -271,6 +272,7 @@ func TestRerun(t *testing.T) { rerunCreatesJob: true, shouldCreateProwJob: true, httpCode: http.StatusFound, + httpMethod: http.MethodPost, }, { name: "User not authorized to create prow job", @@ -279,6 +281,7 @@ func TestRerun(t *testing.T) { rerunCreatesJob: true, shouldCreateProwJob: false, httpCode: http.StatusFound, + httpMethod: http.MethodPost, }, { name: "RerunCreatesJob set to false, should not create prow job", @@ -287,6 +290,7 @@ func TestRerun(t *testing.T) { rerunCreatesJob: false, shouldCreateProwJob: false, httpCode: http.StatusOK, + httpMethod: http.MethodGet, }, { name: "Allow anyone set to true, creates job", @@ -295,6 +299,16 @@ func TestRerun(t *testing.T) { rerunCreatesJob: true, shouldCreateProwJob: true, httpCode: http.StatusFound, + httpMethod: http.MethodPost, + }, + { + name: "Direct rerun disabled, post request", + login: "ugh", + allowAnyone: true, + rerunCreatesJob: false, + shouldCreateProwJob: false, + httpCode: http.StatusMethodNotAllowed, + httpMethod: http.MethodPost, }, } @@ -333,7 +347,7 @@ func TestRerun(t *testing.T) { } } handler := handleRerun(fakeProwJobClient.ProwV1().ProwJobs("prowjobs"), tc.rerunCreatesJob, configGetter) - req, err := http.NewRequest(http.MethodPost, "/rerun?prowjob=wowsuch", nil) + req, err := http.NewRequest(tc.httpMethod, "/rerun?prowjob=wowsuch", nil) req.AddCookie(&http.Cookie{ Name: "github_login", Value: tc.login, @@ -359,7 +373,7 @@ func TestRerun(t *testing.T) { t.Errorf("expected to get two prowjobs, got %d", numPJs) } - } else if !tc.rerunCreatesJob { + } else if !tc.rerunCreatesJob && tc.httpCode == http.StatusOK { resp := rr.Result() defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) diff --git a/prow/cmd/deck/static/prow/prow.ts b/prow/cmd/deck/static/prow/prow.ts index f95373790fe17..37b88bdb44467 100644 --- a/prow/cmd/deck/static/prow/prow.ts +++ b/prow/cmd/deck/static/prow/prow.ts @@ -650,12 +650,12 @@ function redraw(fz: FuzzySearch): void { max = 2 * 3600; } drawJobHistogram(totalJob, jobHistogram, now - (12 * 3600), now, max); - if (rerunStatus === "denied") { + if (rerunStatus === "error") { modal.style.display = "block"; rerunCommand.innerHTML = `You don't have permission to rerun that job. Try asking @cjwagner.`; } else if (rerunStatus === "success") { modal.style.display = "block"; - rerunCommand.innerHTML = `Job successfully triggered`; + rerunCommand.innerHTML = `Job successfully triggered. Wait 30 seconds and refresh the page for the job to show up`; } else if (rerunStatus != null) { modal.style.display = "block"; rerunCommand.innerHTML = `Nice try! The direct rerun feature hasn't been implemented yet, so that button does nothing.`;