go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/led/ledcmd/get_builder.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 ledcmd
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"net/http"
    22  	"strings"
    23  
    24  	bbpb "go.chromium.org/luci/buildbucket/proto"
    25  	swarmbucket "go.chromium.org/luci/common/api/buildbucket/swarmbucket/v1"
    26  	"go.chromium.org/luci/common/api/swarming/swarming/v1"
    27  	"go.chromium.org/luci/common/retry/transient"
    28  	"go.chromium.org/luci/led/job"
    29  	"go.chromium.org/luci/led/job/jobcreate"
    30  	swarmingpb "go.chromium.org/luci/swarming/proto/api_v2"
    31  )
    32  
    33  // GetBuildersOpts are the options for GetBuilder.
    34  type GetBuildersOpts struct {
    35  	BuildbucketHost string
    36  	Project         string
    37  	Bucket          string
    38  	Builder         string
    39  	Canary          bool
    40  	ExtraTags       []string
    41  	PriorityDiff    int
    42  	Experiments     map[string]bool
    43  
    44  	KitchenSupport job.KitchenSupport
    45  	RealBuild      bool
    46  }
    47  
    48  func getBuilderJobName(opts GetBuildersOpts) string {
    49  	return fmt.Sprintf(`get-builder %s:%s`, opts.Bucket, opts.Builder)
    50  }
    51  
    52  // GetBuilder retrieves a new job Definition from a Buildbucket builder.
    53  func GetBuilder(ctx context.Context, authClient *http.Client, opts GetBuildersOpts) (*job.Definition, error) {
    54  	if opts.RealBuild {
    55  		return synthesizeBuildFromBuilder(ctx, authClient, opts)
    56  	}
    57  
    58  	if opts.KitchenSupport == nil {
    59  		opts.KitchenSupport = job.NoKitchenSupport()
    60  	}
    61  
    62  	sbucket := newSwarmbucketClient(authClient, opts.BuildbucketHost)
    63  
    64  	type parameters struct {
    65  		BuilderName     string `json:"builder_name"`
    66  		APIExplorerLink bool   `json:"api_explorer_link"`
    67  	}
    68  
    69  	data, err := json.Marshal(&parameters{opts.Builder, false})
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	canaryPref := "PROD"
    75  	if opts.Canary {
    76  		canaryPref = "CANARY"
    77  	}
    78  
    79  	args := &swarmbucket.LegacySwarmbucketApiGetTaskDefinitionRequestMessage{
    80  		BuildRequest: &swarmbucket.LegacyApiPutRequestMessage{
    81  			CanaryPreference: canaryPref,
    82  			Bucket:           opts.Bucket,
    83  			ParametersJson:   string(data),
    84  			Tags:             opts.ExtraTags,
    85  		},
    86  	}
    87  	answer, err := sbucket.GetTaskDef(args).Context(ctx).Do()
    88  	if err != nil {
    89  		return nil, transient.Tag.Apply(err)
    90  	}
    91  
    92  	newRpcsTask := &swarming.SwarmingRpcsNewTaskRequest{}
    93  	r := strings.NewReader(answer.TaskDefinition)
    94  	if err = json.NewDecoder(r).Decode(newRpcsTask); err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	newTask := toNewTaskRequest(newRpcsTask)
    99  
   100  	jd, err := jobcreate.FromNewTaskRequest(
   101  		ctx, newTask, getBuilderJobName(opts),
   102  		answer.SwarmingHost, opts.KitchenSupport, opts.PriorityDiff, nil, opts.ExtraTags, authClient)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	if err := fillCasDefaults(jd); err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	return jd, nil
   112  }
   113  
   114  func synthesizeBuildFromBuilder(ctx context.Context, authClient *http.Client, opts GetBuildersOpts) (*job.Definition, error) {
   115  	bbClient := newBuildbucketClient(authClient, opts.BuildbucketHost)
   116  	build, err := bbClient.SynthesizeBuild(ctx, &bbpb.SynthesizeBuildRequest{
   117  		Builder: &bbpb.BuilderID{
   118  			Project: opts.Project,
   119  			Bucket:  opts.Bucket,
   120  			Builder: opts.Builder,
   121  		},
   122  		Experiments: opts.Experiments,
   123  	})
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	return jobcreate.FromBuild(build, opts.BuildbucketHost, getBuilderJobName(opts), opts.PriorityDiff, opts.ExtraTags), nil
   128  }
   129  
   130  func toNewTaskRequest(r *swarming.SwarmingRpcsNewTaskRequest) *swarmingpb.NewTaskRequest {
   131  	ret := &swarmingpb.NewTaskRequest{
   132  		Name:            r.Name,
   133  		ParentTaskId:    r.ParentTaskId,
   134  		Priority:        int32(r.Priority),
   135  		TaskSlices:      make([]*swarmingpb.TaskSlice, 0, len(r.TaskSlices)),
   136  		Tags:            r.Tags,
   137  		User:            r.User,
   138  		ServiceAccount:  r.ServiceAccount,
   139  		PubsubTopic:     r.PubsubTopic,
   140  		PubsubAuthToken: r.PubsubAuthToken,
   141  		PubsubUserdata:  r.PubsubUserdata,
   142  		EvaluateOnly:    r.EvaluateOnly,
   143  		PoolTaskTemplate: swarmingpb.NewTaskRequest_PoolTaskTemplateField(
   144  			swarmingpb.NewTaskRequest_PoolTaskTemplateField_value[r.PoolTaskTemplate],
   145  		),
   146  		BotPingToleranceSecs: int32(r.BotPingToleranceSecs),
   147  		RequestUuid:          r.RequestUuid,
   148  		Resultdb:             &swarmingpb.ResultDBCfg{},
   149  		Realm:                r.Realm,
   150  	}
   151  	for _, t := range r.TaskSlices {
   152  		props := t.Properties
   153  		nt := &swarmingpb.TaskSlice{
   154  			Properties: &swarmingpb.TaskProperties{
   155  				Caches: make([]*swarmingpb.CacheEntry, 0, len(props.Caches)),
   156  				CipdInput: &swarmingpb.CipdInput{
   157  					Packages: make([]*swarmingpb.CipdPackage, 0, len(props.CipdInput.Packages)),
   158  				},
   159  				Command:              props.Command,
   160  				RelativeCwd:          props.RelativeCwd,
   161  				Dimensions:           make([]*swarmingpb.StringPair, 0, len(props.Dimensions)),
   162  				Env:                  make([]*swarmingpb.StringPair, 0, len(props.Env)),
   163  				EnvPrefixes:          make([]*swarmingpb.StringListPair, 0, len(props.EnvPrefixes)),
   164  				ExecutionTimeoutSecs: int32(props.ExecutionTimeoutSecs),
   165  				GracePeriodSecs:      int32(props.GracePeriodSecs),
   166  				Idempotent:           props.Idempotent,
   167  				IoTimeoutSecs:        int32(props.IoTimeoutSecs),
   168  				Outputs:              props.Outputs,
   169  				SecretBytes:          []byte(props.SecretBytes),
   170  			},
   171  
   172  			ExpirationSecs:  int32(t.ExpirationSecs),
   173  			WaitForCapacity: t.WaitForCapacity,
   174  		}
   175  		ret.TaskSlices = append(ret.TaskSlices, nt)
   176  		if cir := props.CasInputRoot; cir != nil {
   177  			nt.Properties.CasInputRoot = &swarmingpb.CASReference{
   178  				CasInstance: cir.CasInstance,
   179  			}
   180  			if d := cir.Digest; d != nil {
   181  				nt.Properties.CasInputRoot.Digest = &swarmingpb.Digest{
   182  					Hash:      d.Hash,
   183  					SizeBytes: d.SizeBytes,
   184  				}
   185  			}
   186  		}
   187  		if c := props.Containment; c != nil {
   188  			nt.Properties.Containment = &swarmingpb.Containment{
   189  				ContainmentType: swarmingpb.ContainmentType(
   190  					swarmingpb.ContainmentType_value[c.ContainmentType],
   191  				),
   192  			}
   193  		}
   194  		for _, env := range props.Env {
   195  			nt.Properties.Env = append(nt.Properties.Env, &swarmingpb.StringPair{
   196  				Key:   env.Key,
   197  				Value: env.Value,
   198  			})
   199  		}
   200  
   201  		for _, path := range props.EnvPrefixes {
   202  			nt.Properties.EnvPrefixes = append(nt.Properties.EnvPrefixes, &swarmingpb.StringListPair{
   203  				Key:   path.Key,
   204  				Value: path.Value,
   205  			})
   206  		}
   207  
   208  		for _, cache := range props.Caches {
   209  			nt.Properties.Caches = append(nt.Properties.Caches, &swarmingpb.CacheEntry{
   210  				Name: cache.Name,
   211  				Path: cache.Path,
   212  			})
   213  		}
   214  
   215  		for _, pkg := range props.CipdInput.Packages {
   216  			nt.Properties.CipdInput.Packages = append(nt.Properties.CipdInput.Packages, &swarmingpb.CipdPackage{
   217  				PackageName: pkg.PackageName,
   218  				Version:     pkg.Version,
   219  				Path:        pkg.Path,
   220  			})
   221  		}
   222  
   223  		for _, dim := range props.Dimensions {
   224  			nt.Properties.Dimensions = append(nt.Properties.Dimensions, &swarmingpb.StringPair{
   225  				Key:   dim.Key,
   226  				Value: dim.Value,
   227  			})
   228  		}
   229  	}
   230  	if rdb := r.Resultdb; rdb != nil {
   231  		ret.Resultdb.Enable = rdb.Enable
   232  	}
   233  
   234  	return ret
   235  }