go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/led/ledcli/main.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  	"fmt"
    20  	"os"
    21  	"os/signal"
    22  
    23  	"github.com/maruel/subcommands"
    24  
    25  	"go.chromium.org/luci/auth/client/authcli"
    26  	"go.chromium.org/luci/cipd/version"
    27  	"go.chromium.org/luci/client/versioncli"
    28  	"go.chromium.org/luci/common/cli"
    29  	log "go.chromium.org/luci/common/logging"
    30  	"go.chromium.org/luci/common/logging/gologger"
    31  	"go.chromium.org/luci/hardcoded/chromeinfra"
    32  	"go.chromium.org/luci/led/job"
    33  )
    34  
    35  // userAgent is the HTTP user agent string for led.
    36  var userAgent = "led 1.0.0"
    37  
    38  func init() {
    39  	ver, err := version.GetStartupVersion()
    40  	if err != nil || ver.InstanceID == "" {
    41  		return
    42  	}
    43  	userAgent += fmt.Sprintf(" (%s@%s)", ver.PackageName, ver.InstanceID)
    44  }
    45  
    46  func handleInterruption(ctx context.Context) context.Context {
    47  	ctx, cancel := context.WithCancel(ctx)
    48  	signalC := make(chan os.Signal, 1)
    49  	signal.Notify(signalC, os.Interrupt)
    50  	go func() {
    51  		interrupted := false
    52  		for range signalC {
    53  			if interrupted {
    54  				os.Exit(1)
    55  			}
    56  			interrupted = true
    57  			cancel()
    58  		}
    59  	}()
    60  	return ctx
    61  }
    62  
    63  // Main executes the entire 'led' command line program, including argument
    64  // parsing and exiting the binary.
    65  //
    66  // If you want to support 'kitchen' based swarming tasks, pass an implementation
    67  // of `ks`. The only implementation of `ks` that matters is in the
    68  // 'infra/tools/led2' package.
    69  func Main(ks job.KitchenSupport) {
    70  	if ks == nil {
    71  		ks = job.NoKitchenSupport()
    72  	}
    73  
    74  	defaults := cmdBaseOptions{
    75  		authOpts:       chromeinfra.DefaultAuthOptions(),
    76  		kitchenSupport: ks,
    77  	}
    78  
    79  	var application = cli.Application{
    80  		Name: "led",
    81  		Title: `'LUCI editor' - Multi-service LUCI job debugging tool.
    82  
    83  Allows local modifications to LUCI jobs to be launched directly in swarming.
    84  This is meant to aid in debugging and development for the interaction of
    85  multiple LUCI services:
    86    * buildbucket
    87    * swarming
    88    * isolate
    89    * recipes
    90    * logdog
    91    * milo
    92  
    93  This command is meant to be used multiple times in a pipeline. The flow is
    94  generally:
    95  
    96    get | edit* | launch
    97  
    98  Where the edit step(s) are optional. The output of the commands on stdout is
    99  a JobDefinition JSON document, and the input to the commands is this same
   100  JobDefinition JSON document. At any stage in the pipeline, you may, of course,
   101  hand-edit the JobDefinition.
   102  
   103  Example:
   104    led get-builder bucket_name:builder_name | \
   105      led edit-recipe-bundle -O recipe_engine=/local/recipe_engine > job.json
   106    # edit job.json by hand to inspect
   107    led edit-system -e CHROME_HEADLESS=1 < job.json | \
   108      led launch
   109  
   110  This would pull the recipe job from the named swarming task, then isolate the
   111  recipes from the current working directory (with an override for the
   112  recipe_engine), and inject the isolate hash into the job, saving the result to
   113  job.json. The user thens inspects job.json to look at the full set of flags and
   114  features. After inspecting/editing the job, the user pipes it back through the
   115  edit subcommand to set the swarming envvar $CHROME_HEADLESS=1, and then launches
   116  the edited task back to swarming.
   117  
   118  The source for led lives at:
   119    https://chromium.googlesource.com/infra/infra/+/HEAD/go/src/infra/tools/led
   120  
   121  The spec (as it is) for JobDefinition is at:
   122    https://chromium.googlesource.com/infra/luci/luci-go/+/HEAD/led/job/job.proto
   123  `,
   124  
   125  		Context: func(ctx context.Context) context.Context {
   126  			goLoggerCfg := gologger.LoggerConfig{Out: os.Stderr}
   127  			goLoggerCfg.Format = "[%{level:.1s} %{time:2006-01-02 15:04:05}] %{message}"
   128  			ctx = goLoggerCfg.Use(ctx)
   129  
   130  			ctx = (&log.Config{Level: log.Info}).Set(ctx)
   131  			return handleInterruption(ctx)
   132  		},
   133  
   134  		Commands: []*subcommands.Command{
   135  			// commands to obtain JobDescriptions. These all begin with `get`.
   136  			// TODO(iannucci): `get` to scrape from any URL
   137  			getSwarmCmd(defaults),
   138  			getBuildCmd(defaults),
   139  			getBuilderCmd(defaults),
   140  
   141  			// commands to edit JobDescriptions.
   142  			editCmd(defaults),
   143  			editSystemCmd(defaults),
   144  			editRecipeBundleCmd(defaults),
   145  			editGerritCLCmd(defaults),
   146  			editCrCLCmd(defaults),
   147  			editGitilesCommitCmd(defaults),
   148  			editPayloadCmd(defaults),
   149  
   150  			// commands to edit the raw isolated files.
   151  			editIsolated(defaults),
   152  
   153  			// commands to launch swarming tasks.
   154  			launchCmd(defaults),
   155  			// TODO(iannucci): launch-local to launch locally
   156  			// TODO(iannucci): launch-buildbucket to launch on buildbucket
   157  
   158  			{}, // spacer
   159  
   160  			subcommands.CmdHelp,
   161  			versioncli.CmdVersion(userAgent),
   162  
   163  			{}, // spacer
   164  
   165  			authcli.SubcommandLogin(defaults.authOpts, "auth-login", false),
   166  			authcli.SubcommandLogout(defaults.authOpts, "auth-logout", false),
   167  			authcli.SubcommandInfo(defaults.authOpts, "auth-info", false),
   168  		},
   169  	}
   170  
   171  	os.Exit(subcommands.Run(&application, nil))
   172  }