go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/internal/buildbucket/buildbucket.go (about) 1 // Copyright 2022 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package buildbucket contains logic of interacting with Buildbucket. 16 package buildbucket 17 18 import ( 19 "context" 20 "net/http" 21 22 pb "go.chromium.org/luci/bisection/proto/v1" 23 "go.chromium.org/luci/bisection/util" 24 bbpb "go.chromium.org/luci/buildbucket/proto" 25 "go.chromium.org/luci/common/logging" 26 "go.chromium.org/luci/grpc/prpc" 27 "go.chromium.org/luci/server/auth" 28 "google.golang.org/protobuf/types/known/fieldmaskpb" 29 ) 30 31 const ( 32 bbHost = "cr-buildbucket.appspot.com" 33 ) 34 35 // mockedBBClientKey is the context key indicates using mocked buildbucket client in tests. 36 var mockedBBClientKey = "used in tests only for setting the mock buildbucket client" 37 38 func newBuildsClient(ctx context.Context, host string) (bbpb.BuildsClient, error) { 39 if mockClient, ok := ctx.Value(&mockedBBClientKey).(*bbpb.MockBuildsClient); ok { 40 return mockClient, nil 41 } 42 43 t, err := auth.GetRPCTransport(ctx, auth.AsSelf) 44 if err != nil { 45 return nil, err 46 } 47 return bbpb.NewBuildsPRPCClient( 48 &prpc.Client{ 49 C: &http.Client{Transport: t}, 50 Host: host, 51 Options: prpc.DefaultOptions(), 52 }), nil 53 } 54 55 // Client is the client to communicate with Buildbucket. 56 // It wraps a bbpb.BuildsClient. 57 type Client struct { 58 Client bbpb.BuildsClient 59 } 60 61 // NewClient creates a client to communicate with Buildbucket. 62 func NewClient(ctx context.Context, host string) (*Client, error) { 63 client, err := newBuildsClient(ctx, host) 64 if err != nil { 65 return nil, err 66 } 67 68 return &Client{ 69 Client: client, 70 }, nil 71 } 72 73 // GetBuild returns bbpb.Build for the requested build. 74 func (c *Client) GetBuild(ctx context.Context, req *bbpb.GetBuildRequest) (*bbpb.Build, error) { 75 return c.Client.GetBuild(ctx, req) 76 } 77 78 func (c *Client) SearchBuild(ctx context.Context, req *bbpb.SearchBuildsRequest) (*bbpb.SearchBuildsResponse, error) { 79 return c.Client.SearchBuilds(ctx, req) 80 } 81 82 func (c *Client) ScheduleBuild(ctx context.Context, req *bbpb.ScheduleBuildRequest) (*bbpb.Build, error) { 83 return c.Client.ScheduleBuild(ctx, req) 84 } 85 86 func (c *Client) CancelBuild(ctx context.Context, req *bbpb.CancelBuildRequest) (*bbpb.Build, error) { 87 return c.Client.CancelBuild(ctx, req) 88 } 89 90 func GetBuild(c context.Context, bbid int64, mask *bbpb.BuildMask) (*bbpb.Build, error) { 91 q := &bbpb.GetBuildRequest{ 92 Id: bbid, 93 Mask: mask, 94 } 95 96 cl, err := NewClient(c, bbHost) 97 if err != nil { 98 logging.Errorf(c, "Cannot create Buildbucket client") 99 return nil, err 100 } 101 return cl.GetBuild(c, q) 102 } 103 104 // SearchOlderBuilds searches for builds in the same builder and are older than a reference Build. 105 // More recent builds appear first. The token for the next page of builds is also returned. 106 func SearchOlderBuilds(c context.Context, refBuild *bbpb.Build, mask *bbpb.BuildMask, maxResultSize int32, pageToken string) ([]*bbpb.Build, string, error) { 107 req := &bbpb.SearchBuildsRequest{ 108 Predicate: &bbpb.BuildPredicate{ 109 Builder: refBuild.Builder, 110 Build: &bbpb.BuildRange{ 111 EndBuildId: refBuild.Id, 112 }, 113 }, 114 Mask: mask, 115 PageSize: maxResultSize, 116 PageToken: pageToken, 117 } 118 119 // Create a new buildbucket client 120 cl, err := NewClient(c, bbHost) 121 if err != nil { 122 logging.Errorf(c, "Cannot create Buildbucket client") 123 return nil, "", err 124 } 125 126 // Execute query for older builds 127 res, err := cl.SearchBuild(c, req) 128 if err != nil { 129 return nil, "", err 130 } 131 132 return res.Builds, res.NextPageToken, nil 133 } 134 135 func ScheduleBuild(c context.Context, req *bbpb.ScheduleBuildRequest) (*bbpb.Build, error) { 136 // Create a new buildbucket client 137 cl, err := NewClient(c, bbHost) 138 if err != nil { 139 logging.Errorf(c, "Cannot create Buildbucket client") 140 return nil, err 141 } 142 return cl.ScheduleBuild(c, req) 143 } 144 145 func CancelBuild(c context.Context, bbid int64, reason string) (*bbpb.Build, error) { 146 // Create a new buildbucket client 147 cl, err := NewClient(c, bbHost) 148 if err != nil { 149 logging.Errorf(c, "Cannot create Buildbucket client") 150 return nil, err 151 } 152 req := &bbpb.CancelBuildRequest{ 153 Id: bbid, 154 SummaryMarkdown: reason, 155 } 156 return cl.Client.CancelBuild(c, req) 157 } 158 159 func GetBuildTaskDimension(ctx context.Context, bbid int64) (*pb.Dimensions, error) { 160 build, err := GetBuild(ctx, bbid, &bbpb.BuildMask{ 161 Fields: &fieldmaskpb.FieldMask{ 162 Paths: []string{"infra.swarming.task_dimensions", "infra.backend.task_dimensions"}, 163 }, 164 }) 165 if err != nil { 166 return nil, err 167 } 168 169 return util.ToDimensionsPB(util.GetTaskDimensions(build)), nil 170 }