go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/rpc/get_build.go (about) 1 // Copyright 2020 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 rpc 16 17 import ( 18 "context" 19 "fmt" 20 21 "go.chromium.org/luci/common/errors" 22 "go.chromium.org/luci/grpc/appstatus" 23 24 "go.chromium.org/luci/buildbucket/appengine/common" 25 "go.chromium.org/luci/buildbucket/appengine/internal/perm" 26 "go.chromium.org/luci/buildbucket/appengine/model" 27 "go.chromium.org/luci/buildbucket/bbperms" 28 pb "go.chromium.org/luci/buildbucket/proto" 29 "go.chromium.org/luci/buildbucket/protoutil" 30 ) 31 32 // validateGet validates the given request. 33 func validateGet(req *pb.GetBuildRequest) error { 34 switch { 35 case req.GetId() != 0: 36 if req.Builder != nil || req.BuildNumber != 0 { 37 return errors.Reason("id is mutually exclusive with (builder and build_number)").Err() 38 } 39 case req.GetBuilder() != nil && req.BuildNumber != 0: 40 if err := protoutil.ValidateRequiredBuilderID(req.Builder); err != nil { 41 return errors.Annotate(err, "builder").Err() 42 } 43 default: 44 return errors.Reason("one of id or (builder and build_number) is required").Err() 45 } 46 return nil 47 } 48 49 func getBuildIDByBuildNumber(ctx context.Context, bldr *pb.BuilderID, nbr int32) (int64, error) { 50 addr := fmt.Sprintf("luci.%s.%s/%s/%d", bldr.Project, bldr.Bucket, bldr.Builder, nbr) 51 switch ents, err := model.SearchTagIndex(ctx, "build_address", addr); { 52 case model.TagIndexIncomplete.In(err): 53 // Shouldn't happen because build address is globally unique (exactly one entry in a complete index). 54 return 0, errors.Reason("unexpected incomplete index for build address %q", addr).Err() 55 case err != nil: 56 return 0, err 57 case len(ents) == 0: 58 return 0, perm.NotFoundErr(ctx) 59 case len(ents) == 1: 60 return ents[0].BuildID, nil 61 default: 62 // Shouldn't happen because build address is globally unique and created before the build. 63 return 0, errors.Reason("unexpected number of results for build address %q: %d", addr, len(ents)).Err() 64 } 65 } 66 67 // GetBuild handles a request to retrieve a build. Implements pb.BuildsServer. 68 func (*Builds) GetBuild(ctx context.Context, req *pb.GetBuildRequest) (*pb.Build, error) { 69 if err := validateGet(req); err != nil { 70 return nil, appstatus.BadRequest(err) 71 } 72 m, err := model.NewBuildMask("", req.Fields, req.Mask) 73 if err != nil { 74 return nil, appstatus.BadRequest(errors.Annotate(err, "invalid mask").Err()) 75 } 76 if req.Id == 0 { 77 req.Id, err = getBuildIDByBuildNumber(ctx, req.Builder, req.BuildNumber) 78 if err != nil { 79 return nil, err 80 } 81 } 82 83 bld, err := common.GetBuild(ctx, req.Id) 84 if err != nil { 85 return nil, err 86 } 87 88 // User needs BuildsGet or BuildsGetLimited permission to call this endpoint. 89 readPerm, err := perm.GetFirstAvailablePerm(ctx, bld.Proto.Builder, bbperms.BuildsGet, bbperms.BuildsGetLimited) 90 if err != nil { 91 var readShadowedErr error 92 var shadowedBkt string 93 // Checks if the build is a led build. 94 entities, getInfraErr := common.GetBuildEntities(ctx, req.Id, model.BuildInfraKind) 95 if getInfraErr == nil { 96 infra := entities[0].(*model.BuildInfra) 97 shadowedBkt = infra.Proto.GetLed().GetShadowedBucket() 98 if shadowedBkt != "" && shadowedBkt != bld.Proto.Builder.Bucket { 99 // The build is a led build. Check the use permission from the shadowed 100 // bucket. 101 shadowedBldr := &pb.BuilderID{ 102 Project: bld.Proto.Builder.Project, 103 Bucket: shadowedBkt, 104 Builder: bld.Proto.Builder.Builder, 105 } 106 readPerm, readShadowedErr = perm.GetFirstAvailablePerm(ctx, shadowedBldr, bbperms.BuildsGet, bbperms.BuildsGetLimited) 107 } 108 } 109 if getInfraErr != nil || shadowedBkt == "" || readShadowedErr != nil { 110 // Either there's error getting build infra, or the build is not a led 111 // build, or the user doesn't have read permission in the shadowed bucket 112 // either. 113 // Return the original error. 114 return nil, err 115 } 116 } 117 118 bp, err := bld.ToProto(ctx, m, func(b *pb.Build) error { 119 if readPerm == bbperms.BuildsGet { 120 return nil 121 } 122 return perm.RedactBuild(ctx, nil, b) 123 }) 124 if err != nil { 125 return nil, err 126 } 127 128 return bp, nil 129 130 }