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  }