go.fuchsia.dev/infra@v0.0.0-20240507153436-9b593402251b/cmd/lkg/buildbucket.go (about) 1 // Copyright 2020 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 import ( 8 "context" 9 "errors" 10 "fmt" 11 12 "github.com/golang-collections/collections/set" 13 "go.chromium.org/luci/auth" 14 buildbucketpb "go.chromium.org/luci/buildbucket/proto" 15 "go.chromium.org/luci/grpc/prpc" 16 "google.golang.org/genproto/protobuf/field_mask" 17 ) 18 19 // buildbucketClientWrapper provides utilities for interacting with Buildbucket. 20 type buildbucketClientWrapper struct { 21 client buildbucketpb.BuildsClient 22 } 23 24 // newBuildbucketClient returns an authenticated buildbucketClientWrapper. 25 func newBuildbucketClient(ctx context.Context, authOpts auth.Options, host string) (*buildbucketClientWrapper, error) { 26 authClient, err := auth.NewAuthenticator(ctx, auth.OptionalLogin, authOpts).Client() 27 if err != nil { 28 return nil, err 29 } 30 client := buildbucketpb.NewBuildsPRPCClient(&prpc.Client{ 31 C: authClient, 32 Host: host, 33 }) 34 return &buildbucketClientWrapper{client: client}, nil 35 } 36 37 // GetBuilds gets the latest n successful builds for each input builder, from most to least recent. 38 // The response is in the same order as the order of the input builders. 39 func (c *buildbucketClientWrapper) GetBuilds(ctx context.Context, builders []*buildbucketpb.BuilderID, mask *field_mask.FieldMask, n int) ([][]*buildbucketpb.Build, error) { 40 // Always grab input by default. 41 mask.Paths = append(mask.Paths, "builds.*.input") 42 // Construct requests to execute in batch. 43 reqs := make([]*buildbucketpb.BatchRequest_Request, len(builders)) 44 for i, builder := range builders { 45 reqs[i] = &buildbucketpb.BatchRequest_Request{ 46 Request: &buildbucketpb.BatchRequest_Request_SearchBuilds{ 47 SearchBuilds: &buildbucketpb.SearchBuildsRequest{ 48 Predicate: &buildbucketpb.BuildPredicate{ 49 Builder: builder, 50 Status: buildbucketpb.Status_SUCCESS, 51 }, 52 Fields: mask, 53 PageSize: int32(n), 54 }, 55 }, 56 } 57 } 58 batchResp, err := c.client.Batch(ctx, &buildbucketpb.BatchRequest{Requests: reqs}) 59 if err != nil { 60 return nil, fmt.Errorf("failed to retrieve latest %d successful builds for %s: %w", n, builders, err) 61 } 62 builds := make([][]*buildbucketpb.Build, len(builders)) 63 for i, resp := range batchResp.Responses { 64 switch resp.Response.(type) { 65 case *buildbucketpb.BatchResponse_Response_SearchBuilds: 66 builds[i] = resp.Response.(*buildbucketpb.BatchResponse_Response_SearchBuilds).SearchBuilds.Builds 67 case *buildbucketpb.BatchResponse_Response_Error: 68 return nil, fmt.Errorf("got batch response error: %s", resp.GetError().String()) 69 default: 70 return nil, fmt.Errorf("unexpected response type: %T", resp.Response) 71 } 72 } 73 return builds, nil 74 } 75 76 // accessorFunc accesses a value from a Build. 77 type accessorFunc func(*buildbucketpb.Build) any 78 79 // getLastKnownGood gets the latest common attribute from the input slices of builds. 80 func getLastKnownGood(builds [][]*buildbucketpb.Build, gitilesRef string, f accessorFunc) (any, error) { 81 if len(builds) == 0 { 82 return nil, errors.New("input builds is of length 0") 83 } 84 // Compute common attributes to all slices but the first. 85 var commonAttrSet *set.Set 86 for _, buildSlice := range builds[1:] { 87 attrSet := set.New() 88 for _, build := range buildSlice { 89 buildAttr := f(build) 90 // Skip nil attributes. 91 if buildAttr != nil { 92 attrSet.Insert(buildAttr) 93 } 94 } 95 // Initialize set on first iteration. 96 if commonAttrSet == nil { 97 commonAttrSet = attrSet 98 // Otherwise, take the intersection between this set and the common set. 99 } else { 100 commonAttrSet = attrSet.Intersection(commonAttrSet) 101 } 102 } 103 104 // Iterate through the first slice, returning the first attribute match. 105 for _, build := range builds[0] { 106 // Skip non-matching refs. 107 if gitilesRef != "" && gitilesRef != build.Input.GitilesCommit.Ref { 108 continue 109 } 110 attr := f(build) 111 // For length-1 inputs, the set is nil, so return the first build. 112 if commonAttrSet == nil || commonAttrSet.Has(attr) { 113 return attr, nil 114 } 115 } 116 117 return nil, errors.New("no common last-known-good attribute found") 118 }