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

     1  package cli
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/spf13/cobra"
    11  	"k8s.io/apimachinery/pkg/runtime"
    12  	"k8s.io/cli-runtime/pkg/genericclioptions"
    13  	"sigs.k8s.io/controller-runtime/pkg/client"
    14  	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
    15  
    16  	"github.com/tilt-dev/tilt/internal/analytics"
    17  	"github.com/tilt-dev/tilt/internal/cli/visitor"
    18  	"github.com/tilt-dev/tilt/internal/controllers"
    19  	"github.com/tilt-dev/tilt/internal/engine"
    20  	engineanalytics "github.com/tilt-dev/tilt/internal/engine/analytics"
    21  	"github.com/tilt-dev/tilt/internal/hud"
    22  	"github.com/tilt-dev/tilt/internal/hud/server"
    23  	"github.com/tilt-dev/tilt/internal/store"
    24  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    25  	"github.com/tilt-dev/tilt/pkg/logger"
    26  	"github.com/tilt-dev/tilt/pkg/model"
    27  )
    28  
    29  type updogCmd struct {
    30  	*genericclioptions.FileNameFlags
    31  	genericclioptions.IOStreams
    32  
    33  	Filenames []string
    34  }
    35  
    36  var _ tiltCmd = &updogCmd{}
    37  
    38  func newUpdogCmd(streams genericclioptions.IOStreams) *updogCmd {
    39  	c := &updogCmd{
    40  		IOStreams: streams,
    41  	}
    42  	c.FileNameFlags = &genericclioptions.FileNameFlags{Filenames: &c.Filenames}
    43  	return c
    44  }
    45  
    46  func (c *updogCmd) name() model.TiltSubcommand { return "updog" }
    47  
    48  func (c *updogCmd) register() *cobra.Command {
    49  	cmd := &cobra.Command{
    50  		Use:   "updog",
    51  		Short: "Demo app that runs the Tilt apiserver, applies the config, and streams logs until ctrl-c",
    52  		Long: `Demo app that runs the Tilt apiserver, applies the config, and streams logs until ctrl-c.
    53  
    54  Starts a "blank slate" version of Tilt that only uses the new Tilt API.
    55  
    56  Doesn't execute the Tiltfile.
    57  
    58  For use in demos where we want to bring up the Tilt apiserver with
    59  a set of configs. Here's a youtube video showing it in action:
    60  
    61  https://www.youtube.com/watch?v=dQw4w9WgXcQ
    62  
    63  See https://github.com/tilt-dev/tilt/tree/master/internal/cli/updog-examples
    64  for example configs.
    65  `,
    66  		Example: "tilt alpha updog -f config.yaml",
    67  	}
    68  
    69  	addStartServerFlags(cmd)
    70  	c.FileNameFlags.AddFlags(cmd.Flags())
    71  
    72  	return cmd
    73  }
    74  
    75  func (c *updogCmd) run(ctx context.Context, args []string) error {
    76  
    77  	a := analytics.Get(ctx)
    78  
    79  	cmdTags := engineanalytics.CmdTags(map[string]string{})
    80  	a.Incr("cmd.updog", cmdTags.AsMap())
    81  	defer a.Flush(time.Second)
    82  
    83  	if len(c.Filenames) == 0 {
    84  		return fmt.Errorf("Expected source files with -f")
    85  	}
    86  
    87  	visitors, err := visitor.FromStrings(c.Filenames, c.In)
    88  	if err != nil {
    89  		return fmt.Errorf("Parsing inputs: %v", err)
    90  	}
    91  
    92  	objects, err := visitor.DecodeAll(v1alpha1.NewScheme(), visitors)
    93  	if err != nil {
    94  		return fmt.Errorf("Decoding inputs: %v", err)
    95  	}
    96  
    97  	clientObjects, err := convertToClientObjects(objects)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	deferred := logger.NewDeferredLogger(ctx)
   103  	ctx = redirectLogs(ctx, deferred)
   104  
   105  	log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime))
   106  
   107  	// Force web-mode to prod
   108  	webModeFlag = model.ProdWebMode
   109  
   110  	log.Print("Tilt updog " + buildStamp())
   111  
   112  	deps, err := wireCmdUpdog(ctx, a, nil, "updog", clientObjects)
   113  	if err != nil {
   114  		deferred.SetOutput(deferred.Original())
   115  		return err
   116  	}
   117  
   118  	l := store.NewLogActionLogger(ctx, deps.Upper.Dispatch)
   119  	deferred.SetOutput(l)
   120  	ctx = redirectLogs(ctx, l)
   121  
   122  	// A lot of these parameters don't matter because we don't have any
   123  	// controllers registered.
   124  	err = deps.Upper.Start(ctx, args, deps.TiltBuild,
   125  		"Tiltfile", store.TerminalModeStream, a.UserOpt(), deps.Token,
   126  		string(deps.CloudAddress))
   127  	if err != context.Canceled {
   128  		return err
   129  	} else {
   130  		return nil
   131  	}
   132  }
   133  
   134  // Once the API server starts, create all the objects that were fed in
   135  // on the commandline.
   136  type updogSubscriber struct {
   137  	objects []client.Object
   138  	client  client.Client
   139  }
   140  
   141  var _ store.SetUpper = &updogSubscriber{}
   142  var _ store.Subscriber = &updogSubscriber{}
   143  
   144  func provideUpdogSubscriber(objects []client.Object, client client.Client) *updogSubscriber {
   145  	return &updogSubscriber{
   146  		objects: objects,
   147  		client:  client,
   148  	}
   149  }
   150  
   151  func (s *updogSubscriber) SetUp(ctx context.Context, _ store.RStore) error {
   152  	for _, obj := range s.objects {
   153  		// Create() modifies its object mysteriously, so copy it first
   154  		kind := obj.GetObjectKind().GroupVersionKind().Kind
   155  		name := obj.GetName()
   156  
   157  		err := s.client.Create(ctx, obj)
   158  		if err != nil {
   159  			return err
   160  		}
   161  
   162  		logger.Get(ctx).Infof("loaded %s/%s", strings.ToLower(kind), name)
   163  	}
   164  	return nil
   165  }
   166  func (s *updogSubscriber) OnChange(_ context.Context, _ store.RStore, _ store.ChangeSummary) error {
   167  	return nil
   168  }
   169  
   170  func provideUpdogCmdSubscribers(
   171  	hudsc *server.HeadsUpServerController,
   172  	tscm *controllers.TiltServerControllerManager,
   173  	cb *controllers.ControllerBuilder,
   174  	ts *hud.TerminalStream,
   175  	us *updogSubscriber) []store.Subscriber {
   176  	return append(engine.ProvideSubscribersAPIOnly(hudsc, tscm, cb, ts), us)
   177  }
   178  
   179  func convertToClientObjects(objs []runtime.Object) ([]ctrlclient.Object, error) {
   180  	result := []ctrlclient.Object{}
   181  	for _, obj := range objs {
   182  		clientObj, ok := obj.(ctrlclient.Object)
   183  		if !ok {
   184  			return nil, fmt.Errorf("Unexpected object type: %T", obj)
   185  		}
   186  		result = append(result, clientObj)
   187  	}
   188  	return result, nil
   189  }