blob: dccaecd6de394ef3cc40e7b65cae65408978f11d [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"context"
"errors"
"flag"
"fmt"
"time"
"cloud.google.com/go/datastore"
"go.skia.org/infra/autoroll/go/manual"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/firestore"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/task_driver/go/lib/auth_steps"
"go.skia.org/infra/task_driver/go/lib/checkout"
"go.skia.org/infra/task_driver/go/td"
)
func main() {
var (
projectId = flag.String("project_id", "", "ID of the Google Cloud project.")
taskId = flag.String("task_id", "", "ID of this task.")
taskName = flag.String("task_name", "", "Name of the task.")
output = flag.String("o", "", "If provided, dump a JSON blob of step data to the given file. Prints to stdout if '-' is given.")
local = flag.Bool("local", true, "True if running locally (as opposed to on the bots)")
checkoutFlags = checkout.SetupFlags(nil)
rollerName = flag.String("roller_name", "", "The roller we will use to create the canary with.")
)
ctx := td.StartRun(projectId, taskId, taskName, output, local)
defer td.EndRun(ctx)
if *rollerName == "" {
td.Fatalf(ctx, "--roller_name must be specified")
}
rs, err := checkout.GetRepoState(checkoutFlags)
if err != nil {
td.Fatal(ctx, skerr.Wrap(err))
}
if rs.Issue == "" || rs.Patchset == "" {
td.Fatalf(ctx, "This task driver should be run only as a try bot")
}
// Create token source with scope for datastore access.
ts, err := auth_steps.Init(ctx, *local, auth.SCOPE_USERINFO_EMAIL, datastore.ScopeDatastore)
if err != nil {
td.Fatal(ctx, skerr.Wrap(err))
}
manualRollDB, err := manual.NewDBWithParams(ctx, firestore.FIRESTORE_PROJECT, "production", ts)
if err != nil {
td.Fatal(ctx, skerr.Wrap(err))
}
req := manual.ManualRollRequest{
Requester: *rollerName,
RollerName: *rollerName,
Status: manual.STATUS_PENDING,
Timestamp: firestore.FixTimestamp(time.Now()),
Revision: rs.GetPatchRef(),
DryRun: true,
NoEmail: true,
NoResolveRevision: true,
}
if err := td.Do(ctx, td.Props("Trigger canary roll").Infra(), func(ctx context.Context) error {
return manualRollDB.Put(&req)
}); err != nil {
td.Fatal(ctx, skerr.Wrap(err))
}
// Add documentation link for canary rolls.
td.StepText(ctx, "Canary roll doc", "https://goto.google.com/autoroller-canary-bots")
if err := waitForCanaryRoll(ctx, manualRollDB, req.Id); err != nil {
td.Fatal(ctx, skerr.Wrap(err))
}
}
func waitForCanaryRoll(parentCtx context.Context, manualRollDB manual.DB, rollId string) error {
ctx := td.StartStep(parentCtx, td.Props("Wait for canary roll"))
defer td.EndStep(ctx)
// For writing to the step's log stream.
stdout := td.NewLogStream(ctx, "stdout", td.Info)
// Lets add the roll link only once to step data.
addedRollLinkStepData := false
for {
roll, err := manualRollDB.Get(ctx, rollId)
if err != nil {
return td.FailStep(ctx, fmt.Errorf("Could not find canary roll with ID: %s", rollId))
}
cl := roll.Url
var rollStatus string
if cl == "" {
rollStatus = fmt.Sprintf("Canary roll has status %s", roll.Status)
} else {
if !addedRollLinkStepData {
// Add the roll link to both the current step and it's parent.
td.StepText(ctx, "Canary roll CL", cl)
td.StepText(parentCtx, "Canary roll CL", cl)
addedRollLinkStepData = true
}
rollStatus = fmt.Sprintf("Canary roll [ %s ] has status %s", roll.Url, roll.Status)
}
if _, err := stdout.Write([]byte(rollStatus)); err != nil {
return td.FailStep(ctx, fmt.Errorf("Could not write to stdout: %s", err))
}
if roll.Status == manual.STATUS_COMPLETE {
if roll.Result == manual.RESULT_SUCCESS {
return nil
} else if roll.Result == manual.RESULT_FAILURE {
if cl == "" {
return td.FailStep(ctx, errors.New("Canary roll could not be created. Ask the trooper to investigate (or directly ping rmistry@)."))
}
return td.FailStep(ctx, fmt.Errorf("Canary roll [ %s ] failed", cl))
} else if roll.Result == manual.RESULT_UNKNOWN {
return td.FailStep(ctx, fmt.Errorf("Canary roll [ %s ] completed with an unknown result", cl))
}
}
time.Sleep(30 * time.Second)
}
}