go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/led/ledcli/edit.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  	"net/http"
    20  
    21  	"github.com/maruel/subcommands"
    22  
    23  	"go.chromium.org/luci/auth"
    24  	"go.chromium.org/luci/common/flag/stringlistflag"
    25  	"go.chromium.org/luci/common/flag/stringmapflag"
    26  	"go.chromium.org/luci/led/job"
    27  	swarmingpb "go.chromium.org/luci/swarming/proto/api_v2"
    28  )
    29  
    30  func editCmd(opts cmdBaseOptions) *subcommands.Command {
    31  	return &subcommands.Command{
    32  		UsageLine: "edit [options]",
    33  		ShortDesc: "edits the userland of a JobDescription",
    34  		LongDesc: `Allows common manipulations to a JobDescription.
    35  
    36  Example:
    37  
    38  led get-builder ... |
    39    led edit -d os=Linux -p something=[100] |
    40    led launch
    41  `,
    42  
    43  		CommandRun: func() subcommands.CommandRun {
    44  			ret := &cmdEdit{}
    45  			ret.initFlags(opts)
    46  			return ret
    47  		},
    48  	}
    49  }
    50  
    51  type cmdEdit struct {
    52  	cmdBase
    53  
    54  	dimensions     stringlistflag.Flag
    55  	properties     stringmapflag.Value
    56  	propertiesAuto stringmapflag.Value
    57  	recipeName     string
    58  	experimental   string
    59  	experiments    stringmapflag.Value
    60  
    61  	recipeIsolate string
    62  	recipeCIPDPkg string
    63  	recipeCIPDVer string
    64  
    65  	processedDimensions  job.DimensionEditCommands
    66  	processedExperiments map[string]bool
    67  
    68  	swarmingHost string
    69  	taskName     string
    70  }
    71  
    72  func (c *cmdEdit) initFlags(opts cmdBaseOptions) {
    73  	c.Flags.Var(&c.dimensions, "d",
    74  		"(repeatable) edit a dimension. "+
    75  			"This takes a parameter of `dimension{=,-=,+=}[value[@expiration_secs]]`. "+
    76  			"Specifying '=[value[@expiration_secs]]' will Reset the dimension to the"+
    77  			" Set of values specified with = (repeating this adds to the Set."+
    78  			" To clear the dimension, specify `dimension=`). "+
    79  			"Specifying '-=value' will Delete the value from the dimension. "+
    80  			"Specifying '+=value[@expiration_secs]' will Add that value to the dimension (expiration). "+
    81  			"Operations are applied as Resets, Deletions, Additions. "+
    82  			"If expiration_secs are omitted, all slices will have the dimension.")
    83  
    84  	c.Flags.Var(&c.properties, "p",
    85  		"(repeatable) override a recipe property. This takes a parameter of `property_name=json_value`. "+
    86  			"Providing an empty json_value will remove that property.")
    87  
    88  	c.Flags.Var(&c.propertiesAuto, "pa",
    89  		"(repeatable) override a recipe property, using the recipe engine autoconvert rule. "+
    90  			"This takes a parameter of `property_name=json_value_or_string`. If json_value_or_string "+
    91  			"cannot be decoded as JSON, it will be used verbatim as the property value. "+
    92  			"Providing an empty json_value will remove that property.")
    93  
    94  	c.Flags.StringVar(&c.recipeName, "r", "",
    95  		"override the `recipe` to run.")
    96  
    97  	// These three are used by the 'recipe_engine/led' module to pin the user
    98  	// task across nested led invocations.
    99  	c.Flags.StringVar(&c.recipeIsolate, "rbh", "",
   100  		"DEPRECATED: use `led edit-recipe-bundle` instead."+
   101  			"override the recipe bundle `hash` (if not using CIPD or git). These should be prepared with"+
   102  			" `recipes.py bundle` from the repo containing your desired recipe and then isolating the"+
   103  			" resulting folder contents. The `led edit-recipe-bundle` subcommand does all this"+
   104  			" automatically.")
   105  	c.Flags.StringVar(&c.recipeCIPDPkg, "rpkg", "",
   106  		"DEPRECATED: use `led edit-payload` instead."+
   107  			"override the recipe CIPD `package` (if not using isolated).")
   108  	c.Flags.StringVar(&c.recipeCIPDVer, "rver", "",
   109  		"DEPRECATED: use `led edit-payload` instead."+
   110  			"override the recipe CIPD `version` (if not using isolated).")
   111  
   112  	c.Flags.StringVar(&c.swarmingHost, "S", "",
   113  		"override the swarming `host` to launch the task on (i.e. chromium-swarm.appspot.com).")
   114  
   115  	c.Flags.StringVar(&c.taskName, "name", "",
   116  		"set the task name of the led job as it will show on swarming.")
   117  
   118  	c.Flags.StringVar(&c.experimental, "exp", "",
   119  		"set to `true` or `false` to change the Build.Input.Experimental value. `led` jobs, "+
   120  			"by default, always start as experimental.")
   121  	c.Flags.Var(&c.experiments, "experiment",
   122  		"DEPRECATED: use `led get-build|get-builder -experiment` instead."+
   123  			"(repeatable) enable or disable an experiment. This takes a parameter of `experiment_name=true|false` and "+
   124  			"adds/removes the corresponding experiment. Already enabled experiments are left as is unless they "+
   125  			"are explicitly disabled.")
   126  
   127  	c.cmdBase.initFlags(opts)
   128  }
   129  
   130  func (c *cmdEdit) positionalRange() (min, max int) { return 0, 0 }
   131  func (c *cmdEdit) jobInput() bool                  { return true }
   132  
   133  func (c *cmdEdit) validateFlags(ctx context.Context, _ []string, _ subcommands.Env) (err error) {
   134  	c.processedDimensions, err = job.MakeDimensionEditCommands(c.dimensions)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	if c.processedExperiments, err = processExperiments(c.experiments); err != nil {
   140  		return err
   141  	}
   142  
   143  	return
   144  }
   145  
   146  func (c *cmdEdit) execute(ctx context.Context, _ *http.Client, _ auth.Options, inJob *job.Definition) (out any, err error) {
   147  	err = inJob.Edit(func(je job.Editor) {
   148  		je.EditDimensions(c.processedDimensions)
   149  		if host := c.swarmingHost; host != "" {
   150  			je.SwarmingHostname(c.swarmingHost)
   151  		}
   152  		if c.taskName != "" {
   153  			je.TaskName(c.taskName)
   154  		}
   155  	})
   156  	if err == nil {
   157  		err = inJob.HighLevelEdit(func(je job.HighLevelEditor) {
   158  			je.Properties(c.properties, false)
   159  			je.Properties(c.propertiesAuto, true)
   160  			if c.recipeName != "" {
   161  				je.Properties(map[string]string{"recipe": c.recipeName}, true)
   162  			}
   163  			if c.recipeIsolate != "" || c.recipeCIPDPkg != "" || c.recipeCIPDVer != "" {
   164  				pkg, ver := inJob.HighLevelInfo().TaskPayloadSource()
   165  				if c.recipeIsolate == "" {
   166  					if c.recipeCIPDPkg != "" {
   167  						pkg = c.recipeCIPDPkg
   168  					}
   169  					if c.recipeCIPDVer != "" {
   170  						ver = c.recipeCIPDVer
   171  					}
   172  				} else {
   173  					pkg = ""
   174  					ver = ""
   175  					var digest *swarmingpb.Digest
   176  					if digest, err = job.ToCasDigest(c.recipeIsolate); err != nil {
   177  						return
   178  					}
   179  					je.CASTaskPayload("", &swarmingpb.CASReference{Digest: digest})
   180  				}
   181  				je.TaskPayloadSource(pkg, ver)
   182  			}
   183  			if c.experimental != "" {
   184  				je.Experimental(c.experimental == "true")
   185  			}
   186  			je.Experiments(c.processedExperiments)
   187  		})
   188  	}
   189  	return inJob, err
   190  }
   191  
   192  func (c *cmdEdit) Run(a subcommands.Application, args []string, env subcommands.Env) int {
   193  	return c.doContextExecute(a, c, args, env)
   194  }