| // Copyright 2018 The Fuchsia 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 |
| |
| // TODO(nmulcahey): Implement general Search RPC support in the buildbucket |
| // CLI and remove this + LKGS |
| |
| import ( |
| "context" |
| "flag" |
| "fmt" |
| "log" |
| "os" |
| "strconv" |
| "strings" |
| |
| "go.chromium.org/luci/auth" |
| "go.chromium.org/luci/buildbucket/proto" |
| "go.chromium.org/luci/grpc/prpc" |
| |
| "go.chromium.org/luci/auth/client/authcli" |
| "go.chromium.org/luci/hardcoded/chromeinfra" |
| ) |
| |
| var ( |
| host string |
| builderID string |
| buildSet string |
| buildStatus int |
| output string |
| // LUCI flags used to parse command-line authentication options. |
| authFlags authcli.Flags |
| ) |
| |
| func init() { |
| flag.StringVar(&host, "host", "cr-buildbucket.appspot.com", "the buildbucket host to use (default is cr-buildbucket.appspot.com)") |
| flag.StringVar(&builderID, "builder-id", "", "name of the builders to use as a reference (e.g. fuchsia/ci/garnet-x64)") |
| flag.StringVar(&buildSet, "build-set", "", "a unique buildset id in buildbucket (e.g. commit/gitiles/fuchsia.googlesource.com/topaz/+/e3127e0bd6d57da7a5959ee70eb0a396590e6d53)") |
| flag.IntVar(&buildStatus, "build-status", int(buildbucketpb.Status_SUCCESS), "expected build status (default is 12, SUCCESS). see https://godoc.org/go.chromium.org/luci/buildbucket/proto#Status") |
| flag.StringVar(&output, "output-file", "", "name of the file to write snapshot to (default is stdout)") |
| |
| authFlags = authcli.Flags{} |
| authFlags.Register(flag.CommandLine, chromeinfra.DefaultAuthOptions()) |
| } |
| |
| // praseBuilderID parses a builder ID of the form "fuchsia/ci/garnet-x64". |
| func parseBuilderID(builderID string) (*buildbucketpb.BuilderID, error) { |
| components := strings.SplitN(builderID, "/", 3) |
| if len(components) != 3 { |
| return nil, fmt.Errorf("failed to parse builder ID") |
| } |
| return &buildbucketpb.BuilderID{ |
| Project: components[0], |
| Bucket: components[1], |
| Builder: components[2], |
| }, nil |
| } |
| |
| func getBuildIDForBuilderAndBuildSet(ctx context.Context, host, buildSetID string, builderID *buildbucketpb.BuilderID, buildStatus int32, buildsClient buildbucketpb.BuildsClient) (int64, error) { |
| res, err := buildsClient.SearchBuilds(ctx, &buildbucketpb.SearchBuildsRequest{ |
| Predicate: &buildbucketpb.BuildPredicate{ |
| Builder: builderID, |
| Status: buildbucketpb.Status(buildStatus), |
| Tags: []*buildbucketpb.StringPair{ |
| &buildbucketpb.StringPair{ |
| Key: "buildset", |
| Value: buildSetID, |
| }, |
| }, |
| }, |
| }) |
| if err != nil { |
| return -1, err |
| } |
| if len(res.Builds) == 0 { |
| return -1, fmt.Errorf("No builds returned") |
| } |
| return res.Builds[0].Id, nil |
| } |
| |
| func main() { |
| flag.Parse() |
| |
| if builderID == "" || buildSet == "" { |
| flag.PrintDefaults() |
| return |
| } |
| |
| id, err := parseBuilderID(builderID) |
| if err != nil { |
| log.Fatalf(err.Error()) |
| } |
| |
| opts, err := authFlags.Options() |
| if err != nil { |
| log.Fatalf(err.Error()) |
| } |
| |
| ctx := context.Background() |
| authenticator := auth.NewAuthenticator(ctx, auth.OptionalLogin, opts) |
| client, err := authenticator.Client() |
| if err != nil { |
| log.Fatalf(err.Error()) |
| } |
| |
| buildsClient := buildbucketpb.NewBuildsPRPCClient(&prpc.Client{ |
| C: client, |
| Host: host, |
| }) |
| |
| buildID, err := getBuildIDForBuilderAndBuildSet(ctx, host, buildSet, id, int32(buildStatus), buildsClient) |
| if err != nil { |
| log.Fatalf(err.Error()) |
| } |
| |
| var outputFile *os.File |
| if output == "" { |
| outputFile = os.Stdout |
| } else { |
| outputFile, err = os.Create(output) |
| if err != nil { |
| log.Fatalf(err.Error()) |
| } |
| defer outputFile.Close() |
| } |
| _, err = outputFile.Write([]byte(strconv.FormatInt(buildID, 10))) |
| if err != nil { |
| log.Fatalf("writing output: %v", err) |
| } |
| } |