github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/hud/server/apiserver.go (about) 1 package server 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "path/filepath" 8 "strings" 9 10 "k8s.io/client-go/dynamic" 11 "k8s.io/client-go/rest" 12 "k8s.io/client-go/tools/clientcmd" 13 14 "github.com/tilt-dev/wmclient/pkg/dirs" 15 16 "github.com/tilt-dev/tilt-apiserver/pkg/server/apiserver" 17 "github.com/tilt-dev/tilt-apiserver/pkg/server/builder" 18 "github.com/tilt-dev/tilt-apiserver/pkg/server/options" 19 "github.com/tilt-dev/tilt-apiserver/pkg/server/testdata" 20 "github.com/tilt-dev/tilt/internal/xdg" 21 "github.com/tilt-dev/tilt/pkg/logger" 22 "github.com/tilt-dev/tilt/pkg/model" 23 24 "github.com/akutz/memconn" 25 26 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 27 "github.com/tilt-dev/tilt/pkg/openapi" 28 ) 29 30 // https://twitter.com/ow/status/1356978380198072321 31 // 32 // By default, the API server request limit is 3MB. Certain Helm Charts with 33 // CRDs have bigger payloads than this, so we bumped it to 20MB. 34 // 35 // (Some custom API servers set it to 100MB, see 36 // https://github.com/kubernetes/kubernetes/pull/73805) 37 // 38 // This doesn't mean large 20MB payloads are fine. Iteratively applying a 20MB 39 // payload over and over will slow down the overall system, simply on copying 40 // and encoding/decoding costs alone. 41 // 42 // The underlying apiserver libraries have the ability to set this limit on a 43 // per-resource level (rather than a per-server level). If that's ever exposed, 44 // we should adjust this limit to be higher for KubernetesApply and lower for 45 // other resource types. 46 // 47 // It also might make sense to help the user break up large payloads. For 48 // example, we could automatically split large CRDs into their own resources. 49 const maxRequestBodyBytes = int64(20 * 1024 * 1024) 50 51 type WebListener net.Listener 52 type APIServerPort int 53 54 type APIServerConfig = apiserver.Config 55 56 type DynamicInterface = dynamic.Interface 57 58 func ProvideDefaultConnProvider() apiserver.ConnProvider { 59 return nil 60 } 61 62 func ProvideMemConn() apiserver.ConnProvider { 63 return apiserver.NetworkConnProvider(&memconn.Provider{}, "memu") 64 } 65 66 func ProvideKeyCert(apiServerName model.APIServerName, host model.WebHost, port model.WebPort, base xdg.Base) (options.GeneratableKeyCert, error) { 67 pairName := strings.ReplaceAll(fmt.Sprintf("%s_%d", host, port), string(filepath.Separator), "_") 68 exampleCert, err := base.CacheFile(filepath.Join("certs", string(apiServerName), pairName)) 69 if err != nil { 70 return options.GeneratableKeyCert{}, err 71 } 72 73 return options.GeneratableKeyCert{CertDirectory: filepath.Dir(exampleCert), PairName: pairName}, nil 74 } 75 76 // Uses the kubernetes config-loading library to load 77 // api configs from disk. 78 // 79 // Usually loads from ~/.windmill/config or ~/tilt-dev/config. 80 // 81 // Also allows overriding with the TILT_CONFIG env variable, like 82 // TILT_CONFIG=./path/to/my/config 83 // which is useful when testing CLIs. 84 func ProvideConfigAccess(dir *dirs.TiltDevDir) clientcmd.ConfigAccess { 85 ret := &clientcmd.PathOptions{ 86 GlobalFile: filepath.Join(dir.Root(), "config"), 87 GlobalFileSubpath: filepath.Join(filepath.Dir(dir.Root()), "config"), 88 EnvVar: "TILT_CONFIG", 89 LoadingRules: clientcmd.NewDefaultClientConfigLoadingRules(), 90 } 91 ret.LoadingRules.DoNotResolvePaths = true 92 return ret 93 } 94 95 // Creates a listener for the plain http web server. 96 func ProvideWebListener(host model.WebHost, port model.WebPort) (WebListener, error) { 97 webListener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", string(host), int(port))) 98 if err != nil { 99 if strings.HasSuffix(err.Error(), "address already in use") { 100 return nil, fmt.Errorf("Tilt cannot start because you already have another process on port %d\n"+ 101 "If you want to run multiple Tilt instances simultaneously,\n"+ 102 "use the --port flag or TILT_PORT env variable to set a custom port\nOriginal error: %v", 103 port, err) 104 } 105 return nil, err 106 } 107 return WebListener(webListener), nil 108 } 109 110 // Picks a random port for the APIServer. 111 // 112 // TODO(nick): In the future, we should be able to have the apiserver listen 113 // on other network interfaces, not just loopback. But then we would have to 114 // also setup the KeyCert to identify the server. 115 func ProvideAPIServerPort() (APIServerPort, error) { 116 addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") 117 if err != nil { 118 return 0, err 119 } 120 121 l, err := net.ListenTCP("tcp", addr) 122 if err != nil { 123 return 0, err 124 } 125 defer l.Close() 126 return APIServerPort(l.Addr().(*net.TCPAddr).Port), nil 127 } 128 129 // Configures the Tilt API server. 130 func ProvideTiltServerOptions( 131 ctx context.Context, 132 tiltBuild model.TiltBuild, 133 connProvider apiserver.ConnProvider, 134 token BearerToken, 135 certKey options.GeneratableKeyCert, 136 apiPort APIServerPort) (*APIServerConfig, error) { 137 w := logger.Get(ctx).Writer(logger.DebugLvl) 138 builder := builder.NewServerBuilder(). 139 WithOutputWriter(w). 140 WithBearerToken(string(token)). 141 WithCertKey(certKey) 142 143 for _, obj := range v1alpha1.AllResourceObjects() { 144 builder = builder.WithResourceMemoryStorage(obj, "data") 145 } 146 builder = builder.WithOpenAPIDefinitions("tilt", tiltBuild.Version, openapi.GetOpenAPIDefinitions) 147 148 if connProvider != nil { 149 builder = builder.WithConnProvider(connProvider) 150 } 151 152 if apiPort != 0 { 153 builder = builder.WithBindPort(int(apiPort)) 154 } 155 156 o, err := builder.ToServerOptions() 157 if err != nil { 158 return nil, err 159 } 160 161 if apiPort == 0 { 162 // Fake bind port 163 o.ServingOptions.BindPort = 1 164 } 165 166 err = o.Complete() 167 if err != nil { 168 return nil, err 169 } 170 err = o.Validate(nil) 171 if err != nil { 172 return nil, err 173 } 174 175 config, err := o.Config() 176 if err != nil { 177 return nil, err 178 } 179 180 // Shout-out to kubectl-tree woop woop. 181 // https://github.com/ahmetb/kubectl-tree/blob/3561e74922d29f576698a820b4c003f1dcf691be/cmd/kubectl-tree/rootcmd.go#L75 182 config.GenericConfig.LoopbackClientConfig.QPS = 1000 183 config.GenericConfig.LoopbackClientConfig.Burst = 1000 184 185 config.GenericConfig.MaxRequestBodyBytes = maxRequestBodyBytes 186 return config, nil 187 } 188 189 // Generate the server config, removing options that are not needed for testing. 190 // 191 // 1) Changes http -> https 192 // 2) Skips OpenAPI installation 193 func ProvideTiltServerOptionsForTesting(ctx context.Context) (*APIServerConfig, error) { 194 config, err := ProvideTiltServerOptions(ctx, 195 model.TiltBuild{}, ProvideMemConn(), "corgi-charge", testdata.CertKey(), 0) 196 if err != nil { 197 return nil, err 198 } 199 200 config.GenericConfig.Config.SkipOpenAPIInstallation = true 201 config.GenericConfig.LoopbackClientConfig.TLSClientConfig = rest.TLSClientConfig{} 202 config.GenericConfig.LoopbackClientConfig.Host = 203 strings.Replace(config.GenericConfig.LoopbackClientConfig.Host, "https://", "http://", 1) 204 config.ExtraConfig.ServingInfo.Cert = nil 205 206 return config, nil 207 } 208 209 // Generate the server config, removing options that are not needed for headless mode 210 // (where we don't open up any webserver or apiserver). 211 func ProvideTiltServerOptionsForHeadless(ctx context.Context, keyCert options.GeneratableKeyCert, memconn apiserver.ConnProvider, version model.TiltBuild) (*APIServerConfig, error) { 212 config, err := ProvideTiltServerOptions(ctx, 213 version, memconn, "corgi-charge", keyCert, 0) 214 if err != nil { 215 return nil, err 216 } 217 218 config.GenericConfig.LoopbackClientConfig.TLSClientConfig = rest.TLSClientConfig{} 219 config.GenericConfig.LoopbackClientConfig.Host = 220 strings.Replace(config.GenericConfig.LoopbackClientConfig.Host, "https://", "http://", 1) 221 config.ExtraConfig.ServingInfo.Cert = nil 222 223 return config, nil 224 } 225 226 // Provide a dynamic API client for the Tilt server. 227 func ProvideTiltDynamic(config *APIServerConfig) (DynamicInterface, error) { 228 return dynamic.NewForConfig(config.GenericConfig.LoopbackClientConfig) 229 }