diff --git a/prow/cluster/deck_rbac.yaml b/prow/cluster/deck_rbac.yaml
index 512cac99adc9..da7097b58119 100644
--- a/prow/cluster/deck_rbac.yaml
+++ b/prow/cluster/deck_rbac.yaml
@@ -17,6 +17,10 @@ rules:
verbs:
- get
- list
+ # Required when deck runs with `--rerun-creates-job=true`
+ # **Warning:** Only use this for non-public deck instances, this allows
+ # anyone with access to your Deck instance to create new Prowjobs
+ # - create
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
diff --git a/prow/cluster/starter.yaml b/prow/cluster/starter.yaml
index 155fabc03a03..b6b966005218 100644
--- a/prow/cluster/starter.yaml
+++ b/prow/cluster/starter.yaml
@@ -533,6 +533,10 @@ rules:
verbs:
- get
- list
+ # Required when deck runs with `--rerun-creates-job=true`
+ # **Warning:** Only use this for non-public deck instances, this allows
+ # anyone with access to your Deck instance to create new Prowjobs
+ # - create
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
diff --git a/prow/cmd/deck/main.go b/prow/cmd/deck/main.go
index 2ad4da012260..bd68aef5643f 100644
--- a/prow/cmd/deck/main.go
+++ b/prow/cmd/deck/main.go
@@ -91,6 +91,7 @@ type options struct {
spyglass bool
spyglassFilesLocation string
gcsCredentialsFile string
+ rerunCreatesJob bool
}
func (o *options) Validate() error {
@@ -136,6 +137,7 @@ func gatherOptions(fs *flag.FlagSet, args ...string) options {
fs.StringVar(&o.staticFilesLocation, "static-files-location", "/static", "Path to the static files")
fs.StringVar(&o.templateFilesLocation, "template-files-location", "/template", "Path to the template files")
fs.StringVar(&o.gcsCredentialsFile, "gcs-credentials-file", "", "Path to the GCS credentials file")
+ fs.BoolVar(&o.rerunCreatesJob, "rerun-creates-job", false, "Change the re-run option in Deck to actually create the job. **WARNING:** Only use this with non-public deck instances, otherwise strangers can DOS your Prow instance")
o.kubernetes.AddFlags(fs)
fs.Parse(args)
o.configPath = config.ConfigPath(o.configPath)
@@ -267,7 +269,7 @@ func main() {
mux.Handle("/tide-history", gziphandler.GzipHandler(handleSimpleTemplate(o, cfg, "tide-history.html", nil)))
mux.Handle("/plugins", gziphandler.GzipHandler(handleSimpleTemplate(o, cfg, "plugins.html", nil)))
- indexHandler := handleSimpleTemplate(o, cfg, "index.html", struct{ SpyglassEnabled bool }{o.spyglass})
+ indexHandler := handleSimpleTemplate(o, cfg, "index.html", struct{ SpyglassEnabled, ReRunCreatesJob bool }{o.spyglass, o.rerunCreatesJob})
runLocal := o.pregeneratedData != ""
@@ -392,7 +394,7 @@ func prodOnlyMain(cfg config.Getter, o options, mux *http.ServeMux) *http.ServeM
mux.Handle("/prowjobs.js", gziphandler.GzipHandler(handleProwJobs(ja)))
mux.Handle("/badge.svg", gziphandler.GzipHandler(handleBadge(ja)))
mux.Handle("/log", gziphandler.GzipHandler(handleLog(ja)))
- mux.Handle("/rerun", gziphandler.GzipHandler(handleRerun(prowJobClient)))
+ mux.Handle("/rerun", gziphandler.GzipHandler(handleRerun(prowJobClient, o.rerunCreatesJob)))
mux.Handle("/prowjob", gziphandler.GzipHandler(handleProwJob(prowJobClient)))
if o.spyglass {
@@ -1147,7 +1149,7 @@ func handleProwJob(prowJobClient prowv1.ProwJobInterface) http.HandlerFunc {
}
}
-func handleRerun(prowJobClient prowv1.ProwJobInterface) http.HandlerFunc {
+func handleRerun(prowJobClient prowv1.ProwJobInterface, createProwJob bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("prowjob")
if name == "" {
@@ -1163,8 +1165,26 @@ func handleRerun(prowJobClient prowv1.ProwJobInterface) http.HandlerFunc {
}
return
}
- pjutil := pjutil.NewProwJob(pj.Spec, pj.ObjectMeta.Labels)
- b, err := yaml.Marshal(&pjutil)
+ newPJ := pjutil.NewProwJob(pj.Spec, pj.ObjectMeta.Labels)
+ // Be very careful about this on publicly accessible Prow instances. Even after we have authentication
+ // for the handler, we need CSRF protection.
+ // 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)
+ return
+ }
+ 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
+ }
+ w.WriteHeader(http.StatusNoContent)
+ 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.")
diff --git a/prow/cmd/deck/main_test.go b/prow/cmd/deck/main_test.go
index 9cf124d8b641..1174fa733d93 100644
--- a/prow/cmd/deck/main_test.go
+++ b/prow/cmd/deck/main_test.go
@@ -256,51 +256,84 @@ func TestProwJob(t *testing.T) {
// TestRerun just checks that the result can be unmarshaled properly, has an
// updated status, and has equal spec.
func TestRerun(t *testing.T) {
- fakeProwJobClient := fake.NewSimpleClientset(&prowapi.ProwJob{
- ObjectMeta: metav1.ObjectMeta{
- Name: "wowsuch",
- Namespace: "prowjobs",
- },
- Spec: prowapi.ProwJobSpec{
- Job: "whoa",
- Type: prowapi.PresubmitJob,
- Refs: &prowapi.Refs{
- Org: "org",
- Repo: "repo",
- Pulls: []prowapi.Pull{
- {Number: 1},
- },
- },
+ testCases := []struct {
+ name string
+ shouldCreateProwjob bool
+ }{
+ {
+ name: "Handler returns ProwJob",
+ shouldCreateProwjob: false,
},
- Status: prowapi.ProwJobStatus{
- State: prowapi.PendingState,
+ {
+ name: "Handler creates ProwJob",
+ shouldCreateProwjob: true,
},
- })
- handler := handleRerun(fakeProwJobClient.ProwV1().ProwJobs("prowjobs"))
- req, err := http.NewRequest(http.MethodGet, "/rerun?prowjob=wowsuch", nil)
- if err != nil {
- t.Fatalf("Error making request: %v", err)
- }
- rr := httptest.NewRecorder()
- handler.ServeHTTP(rr, req)
- if rr.Code != http.StatusOK {
- t.Fatalf("Bad error code: %d", rr.Code)
}
- resp := rr.Result()
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatalf("Error reading response body: %v", err)
- }
- var res prowapi.ProwJob
- if err := yaml.Unmarshal(body, &res); err != nil {
- t.Fatalf("Error unmarshaling: %v", err)
- }
- if res.Spec.Job != "whoa" {
- t.Errorf("Wrong job, expected \"whoa\", got \"%s\"", res.Spec.Job)
- }
- if res.Status.State != prowapi.TriggeredState {
- t.Errorf("Wrong state, expected \"%v\", got \"%v\"", prowapi.TriggeredState, res.Status.State)
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ fakeProwJobClient := fake.NewSimpleClientset(&prowapi.ProwJob{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "wowsuch",
+ Namespace: "prowjobs",
+ },
+ Spec: prowapi.ProwJobSpec{
+ Job: "whoa",
+ Type: prowapi.PresubmitJob,
+ Refs: &prowapi.Refs{
+ Org: "org",
+ Repo: "repo",
+ Pulls: []prowapi.Pull{
+ {Number: 1},
+ },
+ },
+ },
+ Status: prowapi.ProwJobStatus{
+ State: prowapi.PendingState,
+ },
+ })
+ handler := handleRerun(fakeProwJobClient.ProwV1().ProwJobs("prowjobs"), tc.shouldCreateProwjob)
+ req, err := http.NewRequest(http.MethodPost, "/rerun?prowjob=wowsuch", nil)
+ if err != nil {
+ t.Fatalf("Error making request: %v", err)
+ }
+ rr := httptest.NewRecorder()
+ handler.ServeHTTP(rr, req)
+
+ if tc.shouldCreateProwjob {
+ if rr.Code != http.StatusNoContent {
+ t.Fatalf("Unexpected http status code: %d, expected 204", rr.Code)
+ }
+ pjs, err := fakeProwJobClient.ProwV1().ProwJobs("prowjobs").List(metav1.ListOptions{})
+ if err != nil {
+ t.Fatalf("failed to list prowjobs: %v", err)
+ }
+ if numPJs := len(pjs.Items); numPJs != 2 {
+ t.Errorf("expected to get two prowjobs, got %d", numPJs)
+ }
+
+ } else {
+ if rr.Code != http.StatusOK {
+ t.Fatalf("Bad error code: %d", rr.Code)
+ }
+ resp := rr.Result()
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ t.Fatalf("Error reading response body: %v", err)
+ }
+ var res prowapi.ProwJob
+ if err := yaml.Unmarshal(body, &res); err != nil {
+ t.Fatalf("Error unmarshaling: %v", err)
+ }
+ if res.Spec.Job != "whoa" {
+ t.Errorf("Wrong job, expected \"whoa\", got \"%s\"", res.Spec.Job)
+ }
+ if res.Status.State != prowapi.TriggeredState {
+ t.Errorf("Wrong state, expected \"%v\", got \"%v\"", prowapi.TriggeredState, res.Status.State)
+ }
+ }
+ })
}
}
diff --git a/prow/cmd/deck/static/prow/prow.ts b/prow/cmd/deck/static/prow/prow.ts
index a66f01910e50..36ecdd3e300b 100644
--- a/prow/cmd/deck/static/prow/prow.ts
+++ b/prow/cmd/deck/static/prow/prow.ts
@@ -6,6 +6,7 @@ import {JobHistogram, JobSample} from './histogram';
declare const allBuilds: Job[];
declare const spyglass: boolean;
+declare const rerunCreatesJob: boolean;
// http://stackoverflow.com/a/5158301/3694
function getParameterByName(name: string): string | null {
@@ -653,8 +654,19 @@ function redraw(fz: FuzzySearch): void {
function createRerunCell(modal: HTMLElement, rerunElement: HTMLElement, prowjob: string): HTMLTableDataCellElement {
const url = `https://${window.location.hostname}/rerun?prowjob=${prowjob}`;
const c = document.createElement("td");
- const i = icon.create("refresh", "Show instructions for rerunning this job");
- i.onclick = () => {
+ let i;
+ if (rerunCreatesJob) {
+ i = icon.create("refresh", "Rerun this job");
+ i.onclick = () => {
+ const form = document.createElement('form');
+ form.method = 'POST';
+ form.action = `${url}`;
+ c.appendChild(form);
+ form.submit();
+ };
+ } else {
+ i = icon.create("refresh", "Show instructions for rerunning this job");
+ i.onclick = () => {
modal.style.display = "block";
rerunElement.innerHTML = `kubectl create -f "${url}"`;
const copyButton = document.createElement('a');
@@ -662,7 +674,8 @@ function createRerunCell(modal: HTMLElement, rerunElement: HTMLElement, prowjob:
copyButton.onclick = () => copyToClipboardWithToast(`kubectl create -f "${url}"`);
copyButton.innerHTML = "file_copy";
rerunElement.appendChild(copyButton);
- };
+ };
+ }
c.appendChild(i);
c.classList.add("icon-cell");
return c;
diff --git a/prow/cmd/deck/template/index.html b/prow/cmd/deck/template/index.html
index c17500fbdc68..5543409143e7 100644
--- a/prow/cmd/deck/template/index.html
+++ b/prow/cmd/deck/template/index.html
@@ -5,6 +5,7 @@
{{end}}