blob: e2aa2f568ed95fb9fcdc67bd8a9dbe4547ec52fd [file] [log] [blame]
// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jobcreate
import (
"context"
"path"
"strings"
"go.chromium.org/luci/buildbucket/cmd/bbagent/bbinput"
swarming "go.chromium.org/luci/common/api/swarming/swarming/v1"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/led/job"
swarmingpb "go.chromium.org/luci/swarming/proto/api"
)
// Returns "bbagent", "kitchen" or "raw" depending on the type of task detected.
func detectMode(r *swarming.SwarmingRpcsNewTaskRequest) string {
arg0, ts := "", &swarming.SwarmingRpcsTaskSlice{}
ts = r.TaskSlices[0]
if ts.Properties != nil {
if len(ts.Properties.Command) > 0 {
arg0 = ts.Properties.Command[0]
}
}
switch arg0 {
case "bbagent${EXECUTABLE_SUFFIX}":
return "bbagent"
case "kitchen${EXECUTABLE_SUFFIX}":
return "kitchen"
}
return "raw"
}
// FromNewTaskRequest generates a new job.Definition by parsing the
// given SwarmingRpcsNewTaskRequest.
//
// If the task's first slice looks like either a bbagent or kitchen-based
// Buildbucket task, the returned Definition will have the `buildbucket`
// field populated, otherwise the `swarming` field will be populated.
func FromNewTaskRequest(ctx context.Context, r *swarming.SwarmingRpcsNewTaskRequest, name, swarmingHost string, ks job.KitchenSupport) (ret *job.Definition, err error) {
if len(r.TaskSlices) == 0 {
return nil, errors.New("swarming tasks without task slices are not supported")
}
ret = &job.Definition{UserPayload: &swarmingpb.CASTree{}}
name = "led: " + name
switch detectMode(r) {
case "bbagent":
bb := &job.Buildbucket{}
ret.JobType = &job.Definition_Buildbucket{Buildbucket: bb}
bbCommonFromTaskRequest(bb, r)
cmd := r.TaskSlices[0].Properties.Command
bb.BbagentArgs, err = bbinput.Parse(cmd[len(cmd)-1])
case "kitchen":
bb := &job.Buildbucket{LegacyKitchen: true}
ret.JobType = &job.Definition_Buildbucket{Buildbucket: bb}
bbCommonFromTaskRequest(bb, r)
err = ks.FromSwarming(ctx, r, bb)
case "raw":
// non-Buildbucket Swarming task
sw := &job.Swarming{Hostname: swarmingHost}
ret.JobType = &job.Definition_Swarming{Swarming: sw}
jobDefinitionFromSwarming(sw, r)
sw.Task.Name = name
default:
panic("impossible")
}
if bb := ret.GetBuildbucket(); err == nil && bb != nil {
bb.Name = name
bb.FinalBuildProtoPath = "build.proto.json"
// set all buildbucket type tasks to experimental by default.
bb.BbagentArgs.Build.Input.Experimental = true
// bump priority by default
bb.BbagentArgs.Build.Infra.Swarming.Priority += 10
// clear fields which don't make sense
bb.BbagentArgs.Build.CanceledBy = ""
bb.BbagentArgs.Build.CreatedBy = ""
bb.BbagentArgs.Build.CreateTime = nil
bb.BbagentArgs.Build.Id = 0
bb.BbagentArgs.Build.Infra.Buildbucket.Hostname = ""
bb.BbagentArgs.Build.Infra.Buildbucket.RequestedProperties = nil
bb.BbagentArgs.Build.Infra.Logdog.Prefix = ""
bb.BbagentArgs.Build.Infra.Swarming.TaskId = ""
bb.BbagentArgs.Build.Number = 0
bb.BbagentArgs.Build.Status = 0
bb.BbagentArgs.Build.UpdateTime = nil
if rdb := bb.BbagentArgs.Build.Infra.GetResultdb(); rdb != nil {
rdb.Invocation = ""
}
// drop the executable path; it's canonically represented by
// out.BBAgentArgs.PayloadPath and out.BBAgentArgs.Build.Exe.
if exePath := bb.BbagentArgs.ExecutablePath; exePath != "" {
// convert to new mode
payload, arg := path.Split(exePath)
bb.BbagentArgs.ExecutablePath = ""
bb.BbagentArgs.PayloadPath = strings.TrimSuffix(payload, "/")
bb.BbagentArgs.Build.Exe.Cmd = []string{arg}
}
dropRecipePackage(&bb.CipdPackages, bb.BbagentArgs.PayloadPath)
props := bb.BbagentArgs.GetBuild().GetInput().GetProperties()
// everything in here is reflected elsewhere in the Build and will be
// re-synthesized by kitchen support or the recipe engine itself, depending
// on the final kitchen/bbagent execution mode.
delete(props.GetFields(), "$recipe_engine/runtime")
// drop legacy recipe fields
if recipe := bb.BbagentArgs.Build.Infra.Recipe; recipe != nil {
bb.BbagentArgs.Build.Infra.Recipe = nil
}
}
// ensure isolate source consistency
for i, slice := range r.TaskSlices {
ir := slice.Properties.InputsRef
if ir == nil {
continue
}
if ret.UserPayload.Digest == "" {
ret.UserPayload.Digest = ir.Isolated
} else if ret.UserPayload.Digest != ir.Isolated {
return nil, errors.Reason("isolate hash inconsistency in slice %d: %q != %q",
i, ret.UserPayload.Digest, ir.Isolated).Err()
}
if ret.UserPayload.Server == "" {
ret.UserPayload.Server = ir.Isolatedserver
} else if ret.UserPayload.Server != ir.Isolatedserver {
return nil, errors.Reason("isolate server inconsistency in slice %d: %q != %q",
i, ret.UserPayload.Server, ir.Isolatedserver).Err()
}
if ret.UserPayload.Namespace == "" {
ret.UserPayload.Namespace = ir.Namespace
} else if ret.UserPayload.Namespace != ir.Namespace {
return nil, errors.Reason("isolate namespace inconsistency in slice %d: %q != %q",
i, ret.UserPayload.Namespace, ir.Namespace).Err()
}
}
return ret, err
}