cmd/retrybuilds: log failures that are being wiped

Saying nothing when a task is completed successfully is a good strategy
for some commands to use. However, it doesn't work well for retrybuilds,
since the command wipes matching failures, but it's not always known in
advance what the matching failures will be.

For similar reasons, add a -dry-run flag, to make it possible to run
the command in dry-run mode and confirm it will do the expected thing
before doing it for real.

For golang/go#34744.

Change-Id: I1517f79c6615bed9b297069daa83b53c927dc9bd
Reviewed-on: https://go-review.googlesource.com/c/build/+/336213
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Alexander Rakoczy <alex@golang.org>
diff --git a/cmd/retrybuilds/retrybuilds.go b/cmd/retrybuilds/retrybuilds.go
index 4e5676f..d6d5c55 100644
--- a/cmd/retrybuilds/retrybuilds.go
+++ b/cmd/retrybuilds/retrybuilds.go
@@ -46,6 +46,7 @@
 )
 
 var (
+	dryRun        = flag.Bool("dry-run", false, "just report what would've been done, without changing anything")
 	masterKeyFile = flag.String("masterkey", filepath.Join(os.Getenv("HOME"), "keys", "gobuilder-master.key"), "path to Go builder master key. If present, the key argument is not necessary")
 	keyFile       = flag.String("key", "", "path to key file")
 	builder       = flag.String("builder", "", "builder to wipe a result for. Empty means all.")
@@ -67,6 +68,7 @@
 }
 
 func main() {
+	log.SetFlags(0)
 	buildenv.RegisterStagingFlag()
 	flag.Parse()
 	*builderPrefix = strings.TrimSuffix(*builderPrefix, "/")
@@ -83,9 +85,11 @@
 		substr := "/log/" + *logHash
 		for _, f := range failures() {
 			if strings.Contains(f.LogURL, substr) {
+				log.Printf("Restarting %+v", f)
 				cl.wipe(f.Builder, f.Hash)
 			}
 		}
+		log.Printf("wiped %d matching failures\n", cl.wiped)
 		return
 	}
 	if *substr != "" {
@@ -95,6 +99,7 @@
 				cl.wipe(f.Builder, f.Hash)
 			}
 		})
+		log.Printf("wiped %d matching failures\n", cl.wiped)
 		return
 	}
 	if *redoFlaky {
@@ -104,6 +109,7 @@
 				cl.wipe(f.Builder, f.Hash)
 			}
 		})
+		log.Printf("wiped %d matching failures\n", cl.wiped)
 		return
 	}
 	if *builder == "" {
@@ -114,11 +120,16 @@
 			if f.Builder != *builder {
 				continue
 			}
+			log.Printf("Restarting %+v", f)
 			cl.wipe(f.Builder, f.Hash)
 		}
+		log.Printf("wiped %d matching failures\n", cl.wiped)
 		return
 	}
-	cl.wipe(*builder, fullHash(*hash))
+	fullHash := fullHash(*hash)
+	log.Printf("Restarting %q", fullHash)
+	cl.wipe(*builder, fullHash)
+	log.Printf("wiped %d matching failures\n", cl.wiped)
 }
 
 func foreachFailure(fn func(f Failure, failLog string)) {
@@ -223,6 +234,7 @@
 
 type client struct {
 	coordinator protos.CoordinatorClient
+	wiped       int // wiped is how many build results have been wiped.
 }
 
 // grpcWipe wipes a git hash failure for the provided builder and hash.
@@ -251,6 +263,7 @@
 			continue
 		}
 		log.Printf("cl.ClearResults(%q, %q) = %v: resp: %v", builder, hash, status.Code(err), resp)
+		c.wiped++
 		return
 	}
 }
@@ -258,6 +271,10 @@
 // wipe wipes the git hash failure for the provided failure.
 // Only the main go repo is currently supported.
 func (c *client) wipe(builder, hash string) {
+	if *dryRun {
+		c.wiped++ // Pretend.
+		return
+	}
 	if *grpcHost != "" {
 		// TODO(golang.org/issue/34744) - Remove HTTP logic after gRPC API for ClearResults is deployed
 		// to the Coordinator.
@@ -297,6 +314,7 @@
 		default:
 			log.Fatalf("Dashboard error: %v", e)
 		case "":
+			c.wiped++
 			return
 		}
 	}