github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/server/server.go (about) 1 /* 2 Copyright 2019 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package server 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "net" 24 "net/http" 25 "strconv" 26 "time" 27 28 "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 29 "google.golang.org/grpc" 30 "google.golang.org/protobuf/encoding/protojson" 31 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event" 34 eventV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event/v2" 35 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log" 36 v2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/server/v2" 37 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" 38 protoV1 "github.com/GoogleContainerTools/skaffold/proto/v1" 39 protoV2 "github.com/GoogleContainerTools/skaffold/proto/v2" 40 ) 41 42 var ( 43 srv *server 44 45 // waits for 1 second before forcing a server shutdown 46 forceShutdownTimeout = 1 * time.Second 47 ) 48 49 type server struct { 50 buildIntentCallback func() 51 syncIntentCallback func() 52 deployIntentCallback func() 53 devloopIntentCallback func() 54 autoBuildCallback func(bool) 55 autoSyncCallback func(bool) 56 autoDeployCallback func(bool) 57 autoDevloopCallback func(bool) 58 } 59 60 func SetBuildCallback(callback func()) { 61 if srv != nil { 62 srv.buildIntentCallback = callback 63 } 64 } 65 66 func SetDevloopCallback(callback func()) { 67 if srv != nil { 68 srv.devloopIntentCallback = callback 69 } 70 } 71 72 func SetDeployCallback(callback func()) { 73 if srv != nil { 74 srv.deployIntentCallback = callback 75 } 76 } 77 78 func SetSyncCallback(callback func()) { 79 if srv != nil { 80 srv.syncIntentCallback = callback 81 } 82 } 83 84 func SetAutoBuildCallback(callback func(bool)) { 85 if srv != nil { 86 srv.autoBuildCallback = callback 87 } 88 } 89 90 func SetAutoDevloopCallback(callback func(bool)) { 91 if srv != nil { 92 srv.autoDevloopCallback = callback 93 } 94 } 95 96 func SetAutoDeployCallback(callback func(bool)) { 97 if srv != nil { 98 srv.autoDeployCallback = callback 99 } 100 } 101 102 func SetAutoSyncCallback(callback func(bool)) { 103 if srv != nil { 104 srv.autoSyncCallback = callback 105 } 106 } 107 108 // Initialize creates the gRPC and HTTP servers for serving the state and event log. 109 // It returns a shutdown callback for tearing down the grpc server, 110 // which the runner is responsible for calling. 111 func Initialize(opts config.SkaffoldOptions) (func() error, error) { 112 emptyCallback := func() error { return nil } 113 if !opts.EnableRPC && opts.RPCPort.Value() == nil && opts.RPCHTTPPort.Value() == nil { 114 log.Entry(context.TODO()).Debug("skaffold API not starting as it's not requested") 115 return emptyCallback, nil 116 } 117 118 preferredGRPCPort := 0 // bind to an available port atomically 119 if opts.RPCPort.Value() != nil { 120 preferredGRPCPort = *opts.RPCPort.Value() 121 } 122 grpcCallback, grpcPort, err := newGRPCServer(preferredGRPCPort) 123 if err != nil { 124 return grpcCallback, fmt.Errorf("starting gRPC server: %w", err) 125 } 126 127 httpCallback := emptyCallback 128 if opts.RPCHTTPPort.Value() != nil { 129 httpCallback, err = newHTTPServer(*opts.RPCHTTPPort.Value(), grpcPort) 130 } 131 callback := func() error { 132 // Optionally pause execution until endpoint hit 133 if opts.WaitForConnection { 134 eventV2.WaitForConnection() 135 } 136 137 httpErr := httpCallback() 138 grpcErr := grpcCallback() 139 errStr := "" 140 if grpcErr != nil { 141 errStr += fmt.Sprintf("grpc callback error: %s\n", grpcErr.Error()) 142 } 143 if httpErr != nil { 144 errStr += fmt.Sprintf("http callback error: %s\n", httpErr.Error()) 145 } 146 if opts.EventLogFile != "" { 147 logFileErr := event.SaveEventsToFile(opts.EventLogFile) 148 if logFileErr != nil { 149 errStr += fmt.Sprintf("event log file error: %s\n", logFileErr.Error()) 150 } 151 152 v2EventLogFile := fmt.Sprintf(`%s.v2`, opts.EventLogFile) 153 logFileV2Err := eventV2.SaveEventsToFile(v2EventLogFile) 154 if logFileV2Err != nil { 155 errStr += fmt.Sprintf("eventV2 log file error: %s\n", logFileV2Err.Error()) 156 } 157 } 158 159 // Save logs from current run to file 160 eventV2.SaveLastLog(opts.LastLogFile) 161 162 return errors.New(errStr) 163 } 164 if err != nil { 165 return callback, fmt.Errorf("starting HTTP server: %w", err) 166 } 167 168 if opts.EnableRPC && opts.RPCPort.Value() == nil && opts.RPCHTTPPort.Value() == nil { 169 log.Entry(context.TODO()).Warnf("started skaffold gRPC API on random port %d", grpcPort) 170 } 171 172 return callback, nil 173 } 174 175 func newGRPCServer(preferredPort int) (func() error, int, error) { 176 l, port, err := listenPort(preferredPort) 177 if err != nil { 178 return func() error { return nil }, 0, fmt.Errorf("creating listener: %w", err) 179 } 180 181 log.Entry(context.TODO()).Infof("starting gRPC server on port %d", port) 182 183 s := grpc.NewServer() 184 srv = &server{ 185 buildIntentCallback: func() {}, 186 deployIntentCallback: func() {}, 187 syncIntentCallback: func() {}, 188 devloopIntentCallback: func() {}, 189 autoBuildCallback: func(bool) {}, 190 autoSyncCallback: func(bool) {}, 191 autoDeployCallback: func(bool) {}, 192 autoDevloopCallback: func(bool) {}, 193 } 194 v2.Srv = &v2.Server{ 195 BuildIntentCallback: func() {}, 196 DeployIntentCallback: func() {}, 197 SyncIntentCallback: func() {}, 198 DevloopIntentCallback: func() {}, 199 AutoBuildCallback: func(bool) {}, 200 AutoSyncCallback: func(bool) {}, 201 AutoDeployCallback: func(bool) {}, 202 AutoDevloopCallback: func(bool) {}, 203 } 204 protoV1.RegisterSkaffoldServiceServer(s, srv) 205 protoV2.RegisterSkaffoldV2ServiceServer(s, v2.Srv) 206 207 go func() { 208 if err := s.Serve(l); err != nil { 209 log.Entry(context.TODO()).Errorf("failed to start grpc server: %s", err) 210 } 211 }() 212 return func() error { 213 ctx, cancel := context.WithTimeout(context.Background(), forceShutdownTimeout) 214 defer cancel() 215 ch := make(chan bool, 1) 216 go func() { 217 s.GracefulStop() 218 ch <- true 219 }() 220 for { 221 select { 222 case <-ctx.Done(): 223 return l.Close() 224 case <-ch: 225 return l.Close() 226 } 227 } 228 }, port, nil 229 } 230 231 func newHTTPServer(preferredPort, proxyPort int) (func() error, error) { 232 mux := runtime.NewServeMux( 233 runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{ 234 Marshaler: &runtime.JSONPb{ 235 MarshalOptions: protojson.MarshalOptions{ 236 UseProtoNames: true, 237 EmitUnpopulated: true, 238 }, 239 UnmarshalOptions: protojson.UnmarshalOptions{ 240 DiscardUnknown: true, 241 }, 242 }, 243 }), 244 ) 245 opts := []grpc.DialOption{grpc.WithInsecure()} 246 err := protoV1.RegisterSkaffoldServiceHandlerFromEndpoint(context.Background(), mux, net.JoinHostPort(util.Loopback, strconv.Itoa(proxyPort)), opts) 247 if err != nil { 248 return func() error { return nil }, err 249 } 250 err = protoV2.RegisterSkaffoldV2ServiceHandlerFromEndpoint(context.Background(), mux, net.JoinHostPort(util.Loopback, strconv.Itoa(proxyPort)), opts) 251 if err != nil { 252 return func() error { return nil }, err 253 } 254 255 l, port, err := listenPort(preferredPort) 256 if err != nil { 257 return func() error { return nil }, fmt.Errorf("creating listener: %w", err) 258 } 259 260 log.Entry(context.TODO()).Infof("starting gRPC HTTP server on port %d (proxying to %d)", port, proxyPort) 261 server := &http.Server{ 262 Handler: mux, 263 } 264 265 go server.Serve(l) 266 267 return func() error { 268 ctx, cancel := context.WithTimeout(context.Background(), forceShutdownTimeout) 269 defer cancel() 270 return server.Shutdown(ctx) 271 }, nil 272 } 273 274 func listenPort(port int) (net.Listener, int, error) { 275 l, err := net.Listen("tcp", net.JoinHostPort(util.Loopback, strconv.Itoa(port))) 276 if err != nil { 277 return nil, 0, err 278 } 279 return l, l.Addr().(*net.TCPAddr).Port, nil 280 }