go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luci_notify/server/treecloser.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 server implements the server to handle pRPC requests. 16 package server 17 18 import ( 19 "context" 20 "fmt" 21 "regexp" 22 23 "google.golang.org/grpc/codes" 24 "google.golang.org/grpc/status" 25 26 "go.chromium.org/luci/gae/service/datastore" 27 28 pb "go.chromium.org/luci/luci_notify/api/service/v1" 29 "go.chromium.org/luci/luci_notify/config" 30 ) 31 32 // TreeCloserServer implements the proto service TreeClosers. 33 type TreeCloserServer struct{} 34 35 // CheckTreeCloser Checks if the builder in CheckTreeCloserRequest is tree-closer or not 36 func (server *TreeCloserServer) CheckTreeCloser(c context.Context, req *pb.CheckTreeCloserRequest) (*pb.CheckTreeCloserResponse, error) { 37 if err := validateRequest(req); err != nil { 38 return nil, err 39 } 40 41 // Query tree closers based on project, bucket 42 ancestorKey := datastore.MakeKey(c, "Project", req.Project, "Builder", fmt.Sprintf("%s/%s", req.Bucket, req.Builder)) 43 q := datastore.NewQuery("TreeCloser").Ancestor(ancestorKey) 44 treeClosers := []*config.TreeCloser{} 45 err := datastore.GetAll(c, q, &treeClosers) 46 47 if err != nil { 48 return nil, status.Errorf(codes.Internal, "couldn't query tree closers: %v", err) 49 } 50 51 if len(treeClosers) == 0 { 52 return &pb.CheckTreeCloserResponse{ 53 IsTreeCloser: false, 54 }, nil 55 } 56 57 for _, treeCloser := range treeClosers { 58 if stepMatchesRule(req.Step, treeCloser.TreeCloser.FailedStepRegexp, treeCloser.TreeCloser.FailedStepRegexpExclude) { 59 return &pb.CheckTreeCloserResponse{ 60 IsTreeCloser: true, 61 }, nil 62 } 63 } 64 return &pb.CheckTreeCloserResponse{ 65 IsTreeCloser: false, 66 }, nil 67 } 68 69 func stepMatchesRule(stepName string, failedStepRegexp string, failedStepRegexpExclude string) bool { 70 var includeRegex *regexp.Regexp 71 if failedStepRegexp != "" { 72 // We should never get an invalid regex here, as our validation should catch this. 73 includeRegex = regexp.MustCompile(fmt.Sprintf("^%s$", failedStepRegexp)) 74 } 75 76 var excludeRegex *regexp.Regexp 77 if failedStepRegexpExclude != "" { 78 excludeRegex = regexp.MustCompile(fmt.Sprintf("^%s$", failedStepRegexpExclude)) 79 } 80 81 return (includeRegex == nil || includeRegex.MatchString(stepName)) && (excludeRegex == nil || !excludeRegex.MatchString(stepName)) 82 } 83 84 func validateRequest(req *pb.CheckTreeCloserRequest) error { 85 if req.Project == "" { 86 return status.Errorf(codes.InvalidArgument, "project cannot be empty") 87 } 88 if req.Bucket == "" { 89 return status.Errorf(codes.InvalidArgument, "bucket cannot be empty") 90 } 91 if req.Builder == "" { 92 return status.Errorf(codes.InvalidArgument, "builder cannot be empty") 93 } 94 if req.Step == "" { 95 return status.Errorf(codes.InvalidArgument, "step cannot be empty") 96 } 97 return nil 98 }