go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/scheduler/appengine/engine/policy/request_builder.go (about) 1 // Copyright 2018 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 policy 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/golang/protobuf/proto" 22 "google.golang.org/protobuf/types/known/structpb" 23 24 buildbucketpb "go.chromium.org/luci/buildbucket/proto" 25 "go.chromium.org/luci/buildbucket/protoutil" 26 bbv1 "go.chromium.org/luci/common/api/buildbucket/buildbucket/v1" 27 "go.chromium.org/luci/common/api/gitiles" 28 "go.chromium.org/luci/common/data/stringset" 29 "go.chromium.org/luci/common/data/strpair" 30 31 "go.chromium.org/luci/scheduler/api/scheduler/v1" 32 "go.chromium.org/luci/scheduler/appengine/internal" 33 "go.chromium.org/luci/scheduler/appengine/task" 34 ) 35 36 // RequestBuilder is a task.Request in a process of being prepared. 37 type RequestBuilder struct { 38 task.Request 39 40 env Environment // for logging 41 } 42 43 // DebugLog adds a line to the request log and the triage log. 44 func (r *RequestBuilder) DebugLog(format string, args ...any) { 45 r.Request.DebugLog += fmt.Sprintf(format+"\n", args...) 46 if r.env != nil { 47 r.env.DebugLog(format, args...) 48 } 49 } 50 51 // FromTrigger derives the request properties from the given trigger. 52 func (r *RequestBuilder) FromTrigger(t *internal.Trigger) { 53 switch p := t.Payload.(type) { 54 case *internal.Trigger_Cron: 55 r.FromCronTrigger(p.Cron) 56 case *internal.Trigger_Webui: 57 r.FromWebUITrigger(p.Webui) 58 case *internal.Trigger_Noop: 59 r.FromNoopTrigger(p.Noop) 60 case *internal.Trigger_Gitiles: 61 r.FromGitilesTrigger(p.Gitiles) 62 case *internal.Trigger_Buildbucket: 63 r.FromBuildbucketTrigger(p.Buildbucket) 64 default: 65 r.DebugLog("Unrecognized trigger payload of type %T, ignoring", p) 66 } 67 } 68 69 // FromCronTrigger derives the request properties from the given cron trigger. 70 func (r *RequestBuilder) FromCronTrigger(t *scheduler.CronTrigger) { 71 // nothing here for now 72 } 73 74 // FromWebUITrigger derives the request properties from the given web UI 75 // trigger. 76 func (r *RequestBuilder) FromWebUITrigger(t *scheduler.WebUITrigger) { 77 // nothing here for now 78 } 79 80 // FromNoopTrigger derives the request properties from the given noop trigger. 81 func (r *RequestBuilder) FromNoopTrigger(t *scheduler.NoopTrigger) { 82 r.Properties = mergeIntoStruct(&structpb.Struct{}, map[string]string{ 83 "noop_trigger_data": t.Data, // for testing 84 }) 85 } 86 87 // FromGitilesTrigger derives the request properties from the given gitiles 88 // trigger. 89 // 90 // TODO(crbug.com/1182002): Remove properties/tags modifications here once there 91 // are no in-flight triggers that might hit Buildbucket v1 code path. 92 func (r *RequestBuilder) FromGitilesTrigger(t *scheduler.GitilesTrigger) { 93 repo, err := gitiles.NormalizeRepoURL(t.Repo, false) 94 if err != nil { 95 r.DebugLog("Bad repo URL %q in the trigger - %s", t.Repo, err) 96 return 97 } 98 99 // Merge properties derived from the commit info on top t.Properties. 100 if t.Properties != nil && len(t.Properties.Fields) != 0 { 101 r.Properties = proto.Clone(t.Properties).(*structpb.Struct) 102 } else { 103 r.Properties = &structpb.Struct{ 104 Fields: make(map[string]*structpb.Value, 3), 105 } 106 } 107 mergeIntoStruct(r.Properties, map[string]string{ 108 "revision": t.Revision, 109 "branch": t.Ref, 110 "repository": t.Repo, 111 }) 112 113 commit := &buildbucketpb.GitilesCommit{ 114 Host: repo.Host, 115 Project: strings.TrimPrefix(repo.Path, "/"), 116 Id: t.Revision, 117 } 118 119 // Join t.Tags with tags derived from the commit. 120 r.Tags = make([]string, 0, len(t.Tags)+3) 121 r.Tags = append( 122 r.Tags, 123 strpair.Format(bbv1.TagBuildSet, protoutil.GitilesBuildSet(commit)), 124 strpair.Format("gitiles_ref", t.Ref), 125 ) 126 r.Tags = append(r.Tags, t.Tags...) 127 r.Tags = removeDups(r.Tags) 128 } 129 130 // FromBuildbucketTrigger derives the request properties from the given 131 // buildbucket trigger. 132 func (r *RequestBuilder) FromBuildbucketTrigger(t *scheduler.BuildbucketTrigger) { 133 r.Properties = t.Properties 134 r.Tags = t.Tags 135 } 136 137 //////////////////////////////////////////////////////////////////////////////// 138 139 // mergeIntoStruct merges `m` into protobuf.Struct returning it. 140 func mergeIntoStruct(s *structpb.Struct, m map[string]string) *structpb.Struct { 141 if s.Fields == nil { 142 s.Fields = make(map[string]*structpb.Value, len(m)) 143 } 144 for k, v := range m { 145 s.Fields[k] = &structpb.Value{ 146 Kind: &structpb.Value_StringValue{StringValue: v}, 147 } 148 } 149 return s 150 } 151 152 // removeDups removes duplicates from the list, modifying it in-place. 153 func removeDups(l []string) []string { 154 seen := stringset.New(len(l)) 155 filtered := l[:0] 156 for _, s := range l { 157 if seen.Add(s) { 158 filtered = append(filtered, s) 159 } 160 } 161 return filtered 162 }