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(¶meters{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 }