go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/led/ledcli/launch.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 ledcli
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"net/http"
    21  	"os"
    22  	"strings"
    23  
    24  	"github.com/maruel/subcommands"
    25  
    26  	"go.chromium.org/luci/auth"
    27  	"go.chromium.org/luci/common/data/text"
    28  	"go.chromium.org/luci/common/logging"
    29  	"go.chromium.org/luci/common/system/terminal"
    30  	"go.chromium.org/luci/led/job"
    31  	"go.chromium.org/luci/led/ledcmd"
    32  	"go.chromium.org/luci/swarming/client/swarming"
    33  )
    34  
    35  func launchCmd(opts cmdBaseOptions) *subcommands.Command {
    36  	return &subcommands.Command{
    37  		UsageLine: "launch",
    38  		ShortDesc: "launches a JobDefinition on swarming",
    39  		LongDesc: `Launches a given JobDefinition on swarming.
    40  
    41  Example:
    42  
    43  led get-builder ... |
    44    led edit ... |
    45    led launch
    46  
    47  If stdout is not a tty (e.g. a file), this command writes a JSON object
    48  containing information about the launched task to stdout.
    49  `,
    50  
    51  		CommandRun: func() subcommands.CommandRun {
    52  			ret := &cmdLaunch{}
    53  			ret.initFlags(opts)
    54  			return ret
    55  		},
    56  	}
    57  }
    58  
    59  type cmdLaunch struct {
    60  	cmdBase
    61  
    62  	modernize     bool
    63  	dump          bool
    64  	noLEDTag      bool
    65  	resultdb      job.RDBEnablement
    66  	realBuild     bool
    67  	boundToParent bool
    68  }
    69  
    70  func (c *cmdLaunch) initFlags(opts cmdBaseOptions) {
    71  	c.Flags.BoolVar(&c.modernize, "modernize", false, "Update the launched task to modern LUCI standards.")
    72  	c.Flags.BoolVar(&c.noLEDTag, "no-led-tag", false, "Don't add user_agent:led tag")
    73  	c.Flags.BoolVar(&c.dump, "dump", false, "Dump swarming task to stdout instead of running it.")
    74  	c.resultdb = ""
    75  	c.Flags.Var(&c.resultdb, "resultdb", text.Doc(`
    76  		Flag for Swarming/ResultDB integration on the launched task. Can be "on" or "off".
    77  		 If "on", resultdb will be forcefully enabled.
    78  		 If "off", resultdb will be forcefully disabled.
    79  		 If unspecified, resultdb will be enabled if the original build had resultdb enabled.`))
    80  	c.Flags.BoolVar(&c.realBuild, "real-build", false, text.Doc(`
    81  		DEPRECATED: Launch a real Buildbucket build instead of a raw swarming task.
    82  		 If the job definition is for a real build, led will launch a real build regardless of this flag.
    83  		 If the job definition is for a raw swarming task but this flag is set, led launch will fail.`))
    84  	c.Flags.BoolVar(&c.boundToParent, "bound-to-parent", false, text.Doc(`
    85  		If the launched job is bound to its parent or not.
    86  		If true, the launched job CANNOT outlive its parent.
    87  		This flag only has effect if the launched job is a real Buildbucket build.`))
    88  	c.cmdBase.initFlags(opts)
    89  }
    90  
    91  func (c *cmdLaunch) jobInput() bool                  { return true }
    92  func (c *cmdLaunch) positionalRange() (min, max int) { return 0, 0 }
    93  
    94  func (c *cmdLaunch) validateFlags(ctx context.Context, _ []string, _ subcommands.Env) (err error) {
    95  	return
    96  }
    97  
    98  func (c *cmdLaunch) execute(ctx context.Context, authClient *http.Client, _ auth.Options, inJob *job.Definition) (out any, err error) {
    99  	uid, err := ledcmd.GetUID(ctx, c.authenticator)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	switch {
   105  	case c.realBuild == inJob.GetBuildbucket().GetRealBuild():
   106  	case !c.realBuild && inJob.GetBuildbucket().GetRealBuild():
   107  		// Likely for `led get-* -real-build | led launch`.
   108  		// We should allow it and treat it as
   109  		// `led get-* -real-build | led launch -real-build`
   110  		logging.Infof(ctx, "Launching the led job as a real build")
   111  		c.realBuild = true
   112  	case c.realBuild && !inJob.GetBuildbucket().GetRealBuild():
   113  		// Likely for `led get-* | led launch -real-build`.
   114  		// Fail it.
   115  		return nil, errors.New("cannot launch a led real build from a legacy job definition")
   116  	}
   117  
   118  	opts := ledcmd.LaunchSwarmingOpts{
   119  		DryRun:           c.dump,
   120  		UserID:           uid,
   121  		FinalBuildProto:  "build.proto.json",
   122  		KitchenSupport:   c.kitchenSupport,
   123  		ParentTaskId:     os.Getenv(swarming.TaskIDEnvVar),
   124  		ResultDB:         c.resultdb,
   125  		NoLEDTag:         c.noLEDTag,
   126  		CanOutliveParent: !c.boundToParent,
   127  	}
   128  
   129  	buildbucketHostname := inJob.GetBuildbucket().GetBbagentArgs().GetBuild().GetInfra().GetBuildbucket().GetHostname()
   130  	swarmingHostname := inJob.Info().SwarmingHostname()
   131  	miloHost := "ci.chromium.org"
   132  	if strings.Contains(buildbucketHostname, "-dev") || strings.Contains(swarmingHostname, "-dev") {
   133  		miloHost = "luci-milo-dev.appspot.com"
   134  	}
   135  
   136  	// Currently modernize only means 'upgrade to bbagent from kitchen'.
   137  	if bb := inJob.GetBuildbucket(); bb != nil {
   138  		if c.modernize {
   139  			bb.LegacyKitchen = false
   140  		}
   141  		if c.realBuild {
   142  			build, err := ledcmd.LaunchBuild(ctx, authClient, inJob, opts)
   143  			if err != nil {
   144  				return nil, err
   145  			}
   146  			if c.dump {
   147  				return build, nil
   148  			}
   149  			logging.Infof(ctx, "LUCI UI: https://%s/b/%d", miloHost, build.Id)
   150  			if !terminal.IsTerminal(int(os.Stdout.Fd())) {
   151  				ret := &struct {
   152  					Buildbucket struct {
   153  						// The id of the launched build.
   154  						BuildID int64 `json:"build_id"`
   155  
   156  						// The hostname of the buildbucket server
   157  						Hostname string `json:"host_name"`
   158  					} `json:"buildbucket"`
   159  				}{}
   160  
   161  				ret.Buildbucket.BuildID = build.Id
   162  				ret.Buildbucket.Hostname = buildbucketHostname
   163  				return ret, nil
   164  			}
   165  			return nil, nil
   166  		}
   167  	}
   168  
   169  	task, meta, err := ledcmd.LaunchSwarming(ctx, authClient, inJob, opts)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	if c.dump {
   174  		return task, nil
   175  	}
   176  
   177  	logging.Infof(ctx, "Launched swarming task: https://%s/task?id=%s",
   178  		swarmingHostname, meta.TaskId)
   179  	logging.Infof(ctx, "LUCI UI: https://%s/swarming/task/%s?server=%s",
   180  		miloHost, meta.TaskId, swarmingHostname)
   181  
   182  	ret := &struct {
   183  		Swarming struct {
   184  			// The swarming task ID of the launched task.
   185  			TaskID string `json:"task_id"`
   186  
   187  			// The hostname of the swarming server
   188  			Hostname string `json:"host_name"`
   189  		} `json:"swarming"`
   190  	}{}
   191  
   192  	if !terminal.IsTerminal(int(os.Stdout.Fd())) {
   193  		ret.Swarming.TaskID = meta.TaskId
   194  		ret.Swarming.Hostname = swarmingHostname
   195  	} else {
   196  		ret = nil
   197  	}
   198  
   199  	return ret, nil
   200  }
   201  
   202  func (c *cmdLaunch) Run(a subcommands.Application, args []string, env subcommands.Env) int {
   203  	return c.doContextExecute(a, c, args, env)
   204  }