cmd/ejobs: add dry-run mode

The -n flags prints actions without running them, which helps with
testing.

Change-Id: I8ad99c5baa7c3e36e8403180f49e4cbf9b3131a2
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/505716
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/ejobs/main.go b/cmd/ejobs/main.go
index 47c3d68..39f211d 100644
--- a/cmd/ejobs/main.go
+++ b/cmd/ejobs/main.go
@@ -34,8 +34,11 @@
 
 const projectID = "go-ecosystem"
 
-var env = flag.String("env", "prod", "worker environment (dev or prod)")
-
+// Common flags
+var (
+	env    = flag.String("env", "prod", "worker environment (dev or prod)")
+	dryRun = flag.Bool("n", false, "print actions but do not execute them")
+)
 var commands = []command{
 	{"list", "",
 		"list jobs", doList},
@@ -109,7 +112,9 @@
 	if err != nil {
 		return err
 	}
-
+	if *dryRun {
+		return nil
+	}
 	rj := reflect.ValueOf(job).Elem()
 	rt := rj.Type()
 	for i := 0; i < rt.NumField(); i++ {
@@ -132,7 +137,9 @@
 	if err != nil {
 		return err
 	}
-
+	if *dryRun {
+		return nil
+	}
 	d7 := -time.Hour * 24 * 7
 	weekBefore := time.Now().Add(d7)
 	tw := tabwriter.NewWriter(os.Stdout, 2, 8, 1, ' ', 0)
@@ -156,7 +163,12 @@
 		return err
 	}
 	for _, jobID := range args {
-		if _, err := httpGet(ctx, workerURL+"/jobs/cancel?jobid="+jobID, ts); err != nil {
+		url := workerURL + "/jobs/cancel?jobid=" + jobID
+		if *dryRun {
+			fmt.Printf("dryrun: GET %s\n", url)
+			continue
+		}
+		if _, err := httpGet(ctx, url, ts); err != nil {
 			return fmt.Errorf("canceling %q: %w", jobID, err)
 		}
 	}
@@ -205,6 +217,10 @@
 	if min >= 0 {
 		url += fmt.Sprintf("&min=%d", min)
 	}
+	if *dryRun {
+		fmt.Printf("dryrun: GET %s\n", url)
+		return nil
+	}
 	body, err := httpGet(ctx, url, its)
 	if err != nil {
 		return err
@@ -248,6 +264,10 @@
 // As an optimization, it skips the upload if the file is already on GCS
 // and has the same checksum as the local file.
 func uploadAnalysisBinary(ctx context.Context, binaryFile string) error {
+	if *dryRun {
+		fmt.Printf("dryrun: upload analysis binary %s\n", binaryFile)
+		return nil
+	}
 	const bucketName = projectID
 	binaryName := filepath.Base(binaryFile)
 	objectName := path.Join("analysis-binaries", binaryName)
@@ -317,7 +337,12 @@
 // requestJSON requests the path from the worker, then reads the returned body
 // and unmarshals it as JSON.
 func requestJSON[T any](ctx context.Context, path string, ts oauth2.TokenSource) (*T, error) {
-	body, err := httpGet(ctx, workerURL+"/"+path, ts)
+	url := workerURL + "/" + path
+	if *dryRun {
+		fmt.Printf("GET %s\n", url)
+		return nil, nil
+	}
+	body, err := httpGet(ctx, url, ts)
 	if err != nil {
 		return nil, err
 	}