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

     1  package cli
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"time"
     8  
     9  	"github.com/spf13/cobra"
    10  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	"k8s.io/cli-runtime/pkg/genericclioptions"
    12  
    13  	"github.com/tilt-dev/tilt/internal/analytics"
    14  	engineanalytics "github.com/tilt-dev/tilt/internal/engine/analytics"
    15  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    16  	"github.com/tilt-dev/tilt/pkg/model"
    17  )
    18  
    19  // A human-friendly CLI for creating cmds.
    20  //
    21  // The name is unfortunate.
    22  //
    23  // (as opposed to the machine-friendly CLIs of create -f or apply -f)
    24  type createCmdCmd struct {
    25  	helper *createHelper
    26  
    27  	dir         string
    28  	env         []string
    29  	filewatches []string
    30  }
    31  
    32  var _ tiltCmd = &createCmdCmd{}
    33  
    34  func newCreateCmdCmd(streams genericclioptions.IOStreams) *createCmdCmd {
    35  	helper := newCreateHelper(streams)
    36  	return &createCmdCmd{
    37  		helper: helper,
    38  	}
    39  }
    40  
    41  func (c *createCmdCmd) name() model.TiltSubcommand { return "create" }
    42  
    43  func (c *createCmdCmd) register() *cobra.Command {
    44  	cmd := &cobra.Command{
    45  		Use:                   "cmd NAME COMMAND [ARG...]",
    46  		DisableFlagsInUseLine: true,
    47  		Short:                 "Create a local command in a running tilt session",
    48  		Long: `Create a local command in a running tilt session.
    49  
    50  Intended to compose with other Tilt APIs that
    51  can restart the command or monitor its status.
    52  
    53  COMMAND should be an executable. A shell script will not work.
    54  
    55  To run a shell script, use 'sh -c' (as shown in the examples).
    56  `,
    57  		Args: cobra.MinimumNArgs(2),
    58  		Example: `
    59  tilt create cmd my-cmd echo hello world
    60  
    61  tilt create cmd my-cmd sh -c "echo hi && echo bye"
    62  `,
    63  	}
    64  
    65  	// Interpret any flags after the object name as part of the
    66  	// command args.
    67  	cmd.Flags().SetInterspersed(false)
    68  
    69  	cmd.Flags().StringVarP(&c.dir, "workdir", "w", "",
    70  		"Working directory of the command. If not specified, uses the current working directory.")
    71  	cmd.Flags().StringArrayVarP(&c.env, "env", "e", nil,
    72  		"Set environment variables in the form NAME=VALUE.")
    73  	cmd.Flags().StringSliceVar(&c.filewatches, "filewatch", nil,
    74  		("Re-run the command whenever the named filewatches detect a change. " +
    75  			"See 'tilt create filewatch' for more."))
    76  
    77  	c.helper.addFlags(cmd)
    78  
    79  	return cmd
    80  }
    81  
    82  func (c *createCmdCmd) run(ctx context.Context, args []string) error {
    83  	a := analytics.Get(ctx)
    84  	cmdTags := engineanalytics.CmdTags(map[string]string{})
    85  	a.Incr("cmd.create-cmd", cmdTags.AsMap())
    86  	defer a.Flush(time.Second)
    87  
    88  	err := c.helper.interpretFlags(ctx)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	cmd, err := c.object(args)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	return c.helper.create(ctx, cmd)
    99  }
   100  
   101  // Interprets the flags specified on the commandline to the Cmd to create.
   102  func (c *createCmdCmd) object(args []string) (*v1alpha1.Cmd, error) {
   103  	name := args[0]
   104  	command := args[1:]
   105  
   106  	dir, err := c.workdir()
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	env := c.env
   112  	restartOn := c.restartOn()
   113  	cmd := v1alpha1.Cmd{
   114  		ObjectMeta: metav1.ObjectMeta{
   115  			Name: name,
   116  		},
   117  		Spec: v1alpha1.CmdSpec{
   118  			Args:      command,
   119  			Dir:       dir,
   120  			Env:       env,
   121  			RestartOn: restartOn,
   122  		},
   123  	}
   124  	return &cmd, nil
   125  }
   126  
   127  // Determine the working directory of the command.
   128  func (c *createCmdCmd) workdir() (string, error) {
   129  	cwd, err := os.Getwd()
   130  	if err != nil {
   131  		return "", err
   132  	}
   133  
   134  	if c.dir == "" {
   135  		return cwd, nil
   136  	}
   137  	if filepath.IsAbs(c.dir) {
   138  		return c.dir, nil
   139  	}
   140  	return filepath.Join(cwd, c.dir), nil
   141  }
   142  
   143  // Determine the restart conditions of the command.
   144  func (c *createCmdCmd) restartOn() *v1alpha1.RestartOnSpec {
   145  	return &v1alpha1.RestartOnSpec{
   146  		FileWatches: c.filewatches,
   147  	}
   148  }