go.fuchsia.dev/infra@v0.0.0-20240507153436-9b593402251b/cmd/buildsetlookup/main.go (about) 1 // Copyright 2018 The Fuchsia Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package main 6 7 // TODO(nmulcahey): Implement general Search RPC support in the buildbucket 8 // CLI and remove this + LKGS 9 10 import ( 11 "context" 12 "flag" 13 "fmt" 14 "log" 15 "os" 16 "strconv" 17 "strings" 18 19 "go.chromium.org/luci/auth" 20 buildbucketpb "go.chromium.org/luci/buildbucket/proto" 21 "go.chromium.org/luci/grpc/prpc" 22 23 "go.chromium.org/luci/auth/client/authcli" 24 "go.chromium.org/luci/hardcoded/chromeinfra" 25 ) 26 27 var ( 28 host string 29 builderID string 30 buildSet string 31 buildStatus int 32 output string 33 // LUCI flags used to parse command-line authentication options. 34 authFlags authcli.Flags 35 ) 36 37 func init() { 38 flag.StringVar(&host, "host", "cr-buildbucket.appspot.com", "the buildbucket host to use (default is cr-buildbucket.appspot.com)") 39 flag.StringVar(&builderID, "builder-id", "", "name of the builders to use as a reference (e.g. fuchsia/ci/garnet-x64)") 40 flag.StringVar(&buildSet, "build-set", "", "a unique buildset id in buildbucket (e.g. commit/gitiles/fuchsia.googlesource.com/topaz/+/e3127e0bd6d57da7a5959ee70eb0a396590e6d53)") 41 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") 42 flag.StringVar(&output, "output-file", "", "name of the file to write snapshot to (default is stdout)") 43 44 authFlags = authcli.Flags{} 45 authFlags.Register(flag.CommandLine, chromeinfra.DefaultAuthOptions()) 46 } 47 48 // praseBuilderID parses a builder ID of the form "fuchsia/ci/garnet-x64". 49 func parseBuilderID(builderID string) (*buildbucketpb.BuilderID, error) { 50 components := strings.SplitN(builderID, "/", 3) 51 if len(components) != 3 { 52 return nil, fmt.Errorf("failed to parse builder ID") 53 } 54 return &buildbucketpb.BuilderID{ 55 Project: components[0], 56 Bucket: components[1], 57 Builder: components[2], 58 }, nil 59 } 60 61 func getBuildIDForBuilderAndBuildSet(ctx context.Context, host, buildSetID string, builderID *buildbucketpb.BuilderID, buildStatus int32, buildsClient buildbucketpb.BuildsClient) (int64, error) { 62 res, err := buildsClient.SearchBuilds(ctx, &buildbucketpb.SearchBuildsRequest{ 63 Predicate: &buildbucketpb.BuildPredicate{ 64 Builder: builderID, 65 Status: buildbucketpb.Status(buildStatus), 66 Tags: []*buildbucketpb.StringPair{ 67 { 68 Key: "buildset", 69 Value: buildSetID, 70 }, 71 }, 72 }, 73 }) 74 if err != nil { 75 return -1, err 76 } 77 if len(res.Builds) == 0 { 78 return -1, fmt.Errorf("No builds returned") 79 } 80 return res.Builds[0].Id, nil 81 } 82 83 func main() { 84 flag.Parse() 85 86 if builderID == "" || buildSet == "" { 87 flag.PrintDefaults() 88 return 89 } 90 91 id, err := parseBuilderID(builderID) 92 if err != nil { 93 log.Fatalf(err.Error()) 94 } 95 96 opts, err := authFlags.Options() 97 if err != nil { 98 log.Fatalf(err.Error()) 99 } 100 101 ctx := context.Background() 102 authenticator := auth.NewAuthenticator(ctx, auth.OptionalLogin, opts) 103 client, err := authenticator.Client() 104 if err != nil { 105 log.Fatalf(err.Error()) 106 } 107 108 buildsClient := buildbucketpb.NewBuildsPRPCClient(&prpc.Client{ 109 C: client, 110 Host: host, 111 }) 112 113 buildID, err := getBuildIDForBuilderAndBuildSet(ctx, host, buildSet, id, int32(buildStatus), buildsClient) 114 if err != nil { 115 log.Fatalf(err.Error()) 116 } 117 118 var outputFile *os.File 119 if output == "" { 120 outputFile = os.Stdout 121 } else { 122 outputFile, err = os.Create(output) 123 if err != nil { 124 log.Fatalf(err.Error()) 125 } 126 defer outputFile.Close() 127 } 128 _, err = outputFile.Write([]byte(strconv.FormatInt(buildID, 10))) 129 if err != nil { 130 log.Fatalf("writing output: %s", err) 131 } 132 }