github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/cli/args.go (about)

     1  package cli
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"fmt"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/kballard/go-shellquote"
    12  	"github.com/pkg/errors"
    13  	"github.com/spf13/cobra"
    14  	"k8s.io/apimachinery/pkg/types"
    15  	"k8s.io/cli-runtime/pkg/genericclioptions"
    16  	"k8s.io/kubectl/pkg/cmd/util/editor"
    17  
    18  	"github.com/tilt-dev/tilt/internal/analytics"
    19  	engineanalytics "github.com/tilt-dev/tilt/internal/engine/analytics"
    20  	"github.com/tilt-dev/tilt/internal/sliceutils"
    21  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    22  	"github.com/tilt-dev/tilt/pkg/logger"
    23  	"github.com/tilt-dev/tilt/pkg/model"
    24  )
    25  
    26  type argsCmd struct {
    27  	streams genericclioptions.IOStreams
    28  	clear   bool
    29  }
    30  
    31  func newArgsCmd(streams genericclioptions.IOStreams) *argsCmd {
    32  	return &argsCmd{
    33  		streams: streams,
    34  	}
    35  }
    36  
    37  func (c *argsCmd) name() model.TiltSubcommand { return "args" }
    38  
    39  func (c *argsCmd) register() *cobra.Command {
    40  	cmd := &cobra.Command{
    41  		Use:                   "args [<flags>] [-- <Tiltfile args>]",
    42  		DisableFlagsInUseLine: true,
    43  		Short:                 "Changes the Tiltfile args in use by a running Tilt",
    44  		Long: `Changes the Tiltfile args in use by a running Tilt.
    45  
    46  The "args" can be used to specify what resources Tilt enables. They can also 
    47  be used to define custom key-value pairs that can be accessed in a Tiltfile
    48  by calling "config.parse()".
    49  
    50  If no args are explicitly specified, (i.e., just "tilt args"), it opens the current args for editing in
    51  the editor defined by your TILT_EDITOR or EDITOR environment variables, or fall back to
    52  an OS-appropriate default.
    53  
    54  Note that Tiltfile arguments do not affect built-in Tilt args (i.e., the things that show up in "tilt up --help", such as "--legacy", "--port"), and they
    55  are defined after built-in args, following a "--".`,
    56  		Example: `# Set new args
    57  tilt args frontend_service backend_service -- --debug on
    58  
    59  # Edit the current args
    60  tilt args
    61  
    62  # Use an alternative editor
    63  EDITOR=nano tilt args`,
    64  	}
    65  
    66  	addConnectServerFlags(cmd)
    67  	cmd.Flags().BoolVar(&c.clear, "clear", false, "Clear the Tiltfile args, as if you'd run tilt with no args")
    68  
    69  	return cmd
    70  }
    71  
    72  func parseEditResult(b []byte) ([]string, error) {
    73  	sc := bufio.NewScanner(bytes.NewReader(b))
    74  	var argsLine *string
    75  	for sc.Scan() {
    76  		line := strings.TrimSpace(sc.Text())
    77  		if len(line) == 0 || line[0] == '#' {
    78  			continue
    79  		}
    80  		if argsLine != nil {
    81  			return nil, errors.New("cannot have multiple non-comment lines")
    82  		}
    83  		s := line
    84  		argsLine = &s
    85  	}
    86  	if argsLine == nil {
    87  		return nil, errors.New("must have exactly one non-comment line, found zero. If you want to clear the args, use `tilt args --clear`")
    88  	}
    89  	args, err := shellquote.Split(*argsLine)
    90  	if err != nil {
    91  		return nil, errors.Wrapf(err, "error parsing %q", string(b))
    92  	}
    93  
    94  	return args, nil
    95  }
    96  
    97  func (c *argsCmd) run(ctx context.Context, args []string) error {
    98  	ctx = logger.WithLogger(ctx, logger.NewLogger(logger.Get(ctx).Level(), c.streams.ErrOut))
    99  
   100  	ctrlclient, err := newClient(ctx)
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	var tf v1alpha1.Tiltfile
   106  	err = ctrlclient.Get(ctx, types.NamespacedName{Name: model.MainTiltfileManifestName.String()}, &tf)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	tags := make(engineanalytics.CmdTags)
   112  	if c.clear {
   113  		if len(args) != 0 {
   114  			return errors.New("--clear cannot be specified with other values")
   115  		}
   116  		args = nil
   117  		tags["clear"] = "true"
   118  	} else if len(args) == 0 {
   119  		input := fmt.Sprintf("# edit args for the running Tilt here\n%s\n", shellquote.Join(tf.Spec.Args...))
   120  		e := editor.NewDefaultEditor([]string{"TILT_EDITOR", "EDITOR"})
   121  		b, _, err := e.LaunchTempFile("", "", strings.NewReader(input))
   122  		if err != nil {
   123  			return err
   124  		}
   125  
   126  		args, err = parseEditResult(b)
   127  		if err != nil {
   128  			return err
   129  		}
   130  		tags["edit"] = "true"
   131  	} else {
   132  		tags["set"] = "true"
   133  	}
   134  
   135  	a := analytics.Get(ctx)
   136  	a.Incr("cmd.args", tags.AsMap())
   137  	defer a.Flush(time.Second)
   138  
   139  	if sliceutils.StringSliceEquals(tf.Spec.Args, args) {
   140  		logger.Get(ctx).Infof("Tilt is already running with those args -- no action taken")
   141  		return nil
   142  	}
   143  	tf.Spec.Args = args
   144  
   145  	err = ctrlclient.Update(ctx, &tf)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	logger.Get(ctx).Infof("Changed config args for Tilt running at %s to %v", apiHost(), args)
   151  
   152  	return nil
   153  }