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 }