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 }