cmd/relui: add post-submit bypass to "tag single x/ repo" workflow
Requires adding a checkbox parameter type. This should allow the release
coordinator (or whoever is running this workflow) to tag a single x/
repo without needing to wait for a green post-submit commit, which is
necessary when doing a security release.
Change-Id: Id9e6743bc5d86730bb109cda91164190c34187c7
Reviewed-on: https://go-review.googlesource.com/c/build/+/517235
Reviewed-by: Heschi Kreinick <heschi@google.com>
Auto-Submit: Roland Shoemaker <roland@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
diff --git a/internal/relui/templates/new_workflow.html b/internal/relui/templates/new_workflow.html
index 956d6d8..9cbd4cc 100644
--- a/internal/relui/templates/new_workflow.html
+++ b/internal/relui/templates/new_workflow.html
@@ -109,6 +109,15 @@
</button>
</div>
</div>
+ {{else if eq $p.Type.String "bool"}}
+ <div class="NewWorkflow-parameter NewWorkflow-parameter--bool">
+ <label for="workflow.params.{{$p.Name}}" title="{{$p.Doc}}">{{$p.Name}}</label>
+ <input
+ id="workflow.params.{{$p.Name}}"
+ name="workflow.params.{{$p.Name}}"
+ {{- with $p.HTMLInputType}}type="{{.}}"{{end}}
+ {{- if $p.RequireNonZero}}required{{end}} />
+ </div>
{{else}}
<div class="NewWorkflow-parameter">
<label title="{{$p.Doc}}">{{$p.Name}}</label>
diff --git a/internal/relui/web.go b/internal/relui/web.go
index 791a3f7..73b76e8 100644
--- a/internal/relui/web.go
+++ b/internal/relui/web.go
@@ -371,6 +371,23 @@
return
}
params[p.Name()] = v
+ case "bool":
+ vStr := r.FormValue(fmt.Sprintf("workflow.params.%s", p.Name()))
+ var v bool
+ switch vStr {
+ case "on":
+ v = true
+ case "":
+ v = false
+ default:
+ http.Error(w, fmt.Sprintf("parameter %q has an unexpected value %q", p.Name(), vStr), http.StatusBadRequest)
+ return
+ }
+ if err := p.Valid(v); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ params[p.Name()] = v
default:
http.Error(w, fmt.Sprintf("parameter %q has an unsupported type %q", p.Name(), p.Type()), http.StatusInternalServerError)
return
diff --git a/internal/task/tagx.go b/internal/task/tagx.go
index 2a8b2cb..7fb02dd 100644
--- a/internal/task/tagx.go
+++ b/internal/task/tagx.go
@@ -54,7 +54,10 @@
reviewers := wf.Param(wd, reviewersParam)
repos := wf.Task0(wd, "Load all repositories", x.SelectRepos)
name := wf.Param(wd, wf.ParamDef[string]{Name: "Repository name", Example: "tools"})
- wf.Expand3(wd, "Create single-repo plan", x.BuildSingleRepoPlan, repos, name, reviewers)
+ // TODO: optional is required to avoid the "required" check, but since it's a checkbox
+ // it's obviously yes/no, should probably be exempted from that check.
+ skipPostSubmit := wf.Param(wd, wf.ParamDef[bool]{Name: "Skip post submit result (optional)", ParamType: wf.Bool})
+ wf.Expand4(wd, "Create single-repo plan", x.BuildSingleRepoPlan, repos, name, skipPostSubmit, reviewers)
return wd
}
@@ -247,7 +250,7 @@
if _, ok := updated[repo.ModPath]; ok {
continue
}
- dep, ok := x.planRepo(wd, repo, updated, reviewers)
+ dep, ok := x.planRepo(wd, repo, updated, reviewers, false)
if !ok {
continue
}
@@ -274,7 +277,7 @@
return nil
}
-func (x *TagXReposTasks) BuildSingleRepoPlan(wd *wf.Definition, repoSlice []TagRepo, name string, reviewers []string) error {
+func (x *TagXReposTasks) BuildSingleRepoPlan(wd *wf.Definition, repoSlice []TagRepo, name string, skipPostSubmit bool, reviewers []string) error {
repos := map[string]TagRepo{}
updatedRepos := map[string]wf.Value[TagRepo]{}
for _, r := range repoSlice {
@@ -288,7 +291,7 @@
if !ok {
return fmt.Errorf("no repository %q", name)
}
- tagged, ok := x.planRepo(wd, repo, updatedRepos, reviewers)
+ tagged, ok := x.planRepo(wd, repo, updatedRepos, reviewers, skipPostSubmit)
if !ok {
return fmt.Errorf("%q doesn't have all of its dependencies (%q)", repo.Name, repo.Deps)
}
@@ -299,7 +302,7 @@
// planRepo adds tasks to wf to update and tag repo. It returns a Value
// containing the tagged repository's information, or nil, false if its
// dependencies haven't been planned yet.
-func (x *TagXReposTasks) planRepo(wd *wf.Definition, repo TagRepo, updated map[string]wf.Value[TagRepo], reviewers []string) (_ wf.Value[TagRepo], ready bool) {
+func (x *TagXReposTasks) planRepo(wd *wf.Definition, repo TagRepo, updated map[string]wf.Value[TagRepo], reviewers []string, skipPostSubmit bool) (_ wf.Value[TagRepo], ready bool) {
var deps []wf.Value[TagRepo]
for _, repoDeps := range repo.Deps {
if dep, ok := updated[repoDeps]; ok {
@@ -319,8 +322,10 @@
cl := wf.Task3(wd, "mail updated go.mod", x.MailGoMod, repoName, gomod, wf.Const(reviewers))
tagCommit = wf.Task3(wd, "wait for submit", x.AwaitGoMod, cl, repoName, branch)
}
- greenCommit := wf.Task2(wd, "wait for green post-submit", x.AwaitGreen, wf.Const(repo), tagCommit)
- tagged := wf.Task2(wd, "tag if appropriate", x.MaybeTag, wf.Const(repo), greenCommit)
+ if !skipPostSubmit {
+ tagCommit = wf.Task2(wd, "wait for green post-submit", x.AwaitGreen, wf.Const(repo), tagCommit)
+ }
+ tagged := wf.Task2(wd, "tag if appropriate", x.MaybeTag, wf.Const(repo), tagCommit)
return tagged, true
}
diff --git a/internal/task/tagx_test.go b/internal/task/tagx_test.go
index 05ac67e..cda45e0 100644
--- a/internal/task/tagx_test.go
+++ b/internal/task/tagx_test.go
@@ -344,7 +344,7 @@
tagXTasks *TagXReposTasks
}
-func newTagXTestDeps(t *testing.T, repos ...*FakeRepo) *tagXTestDeps {
+func newTagXTestDeps(t *testing.T, dashboardStatus string, repos ...*FakeRepo) *tagXTestDeps {
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
t.Skip("Requires bash shell scripting support.")
}
@@ -367,10 +367,11 @@
fakeBuildlets := NewFakeBuildlets(t, "", nil)
fakeGerrit := NewFakeGerrit(t, repos...)
- var builders, allOK []string
+ var builders, dashboardStatuses []string
for _, b := range dashboard.Builders {
builders = append(builders, b.Name)
- allOK = append(allOK, "ok")
+ // allOK = append(allOK, "ok")
+ dashboardStatuses = append(dashboardStatuses, dashboardStatus)
}
fakeDash := func(repo string) *types.BuildStatus {
if repo == "" {
@@ -397,7 +398,7 @@
Date: time.Now().Format(time.RFC3339),
Branch: "master",
GoBranch: "master",
- Results: allOK,
+ Results: dashboardStatuses,
})
}
return st
@@ -447,7 +448,7 @@
})
tools.Tag("v1.1.5", tools1)
- deps := newTagXTestDeps(t, sys, mod, tools)
+ deps := newTagXTestDeps(t, "ok", sys, mod, tools)
wd := deps.tagXTasks.NewDefinition()
w, err := workflow.Start(wd, map[string]interface{}{
@@ -502,7 +503,7 @@
}
}
-func TestTagSingleRepo(t *testing.T) {
+func testTagSingleRepo(t *testing.T, dashboardStatus string, skipPostSubmit bool) {
mod := NewFakeRepo(t, "mod")
mod1 := mod.Commit(map[string]string{
"go.mod": "module golang.org/x/mod\n",
@@ -519,14 +520,20 @@
"main.go": "package main",
})
- deps := newTagXTestDeps(t, mod, foo)
+ deps := newTagXTestDeps(t, dashboardStatus, mod, foo)
wd := deps.tagXTasks.NewSingleDefinition()
ctx, cancel := context.WithTimeout(deps.ctx, time.Minute)
- w, err := workflow.Start(wd, map[string]interface{}{
+ args := map[string]interface{}{
"Repository name": "foo",
reviewersParam.Name: []string(nil),
- })
+ }
+ if skipPostSubmit {
+ args["Skip post submit result (optional)"] = true
+ } else {
+ args["Skip post submit result (optional)"] = false
+ }
+ w, err := workflow.Start(wd, args)
if err != nil {
t.Fatal(err)
}
@@ -549,6 +556,12 @@
}
}
+func TestTagSingleRepo(t *testing.T) {
+ t.Run("with post-submit check", func(t *testing.T) { testTagSingleRepo(t, "ok", false) })
+ // If skipPostSubmit is false, AwaitGreen should sit an spin for a minute before failing
+ t.Run("without post-submit check", func(t *testing.T) { testTagSingleRepo(t, "bad", true) })
+}
+
type verboseListener struct {
t *testing.T
outputListener func(string, interface{})
diff --git a/internal/workflow/workflow.go b/internal/workflow/workflow.go
index 680fd47..f9fd858 100644
--- a/internal/workflow/workflow.go
+++ b/internal/workflow/workflow.go
@@ -223,6 +223,12 @@
SliceLong = ParamType[[]string]{
HTMLElement: "textarea",
}
+
+ // Checkbox bool parameter
+ Bool = ParamType[bool]{
+ HTMLElement: "input",
+ HTMLInputType: "checkbox",
+ }
)
// Param registers a new parameter p that is filled in at