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  }