...

Source file src/github.com/redhat-developer/odo/pkg/apiserver-impl/starterserver.go

Documentation: github.com/redhat-developer/odo/pkg/apiserver-impl

     1  package apiserver_impl
     2  
     3  import (
     4  	"context"
     5  	"embed"
     6  	"fmt"
     7  	"io/fs"
     8  	"net"
     9  	"net/http"
    10  
    11  	"k8s.io/klog"
    12  
    13  	openapi "github.com/redhat-developer/odo/pkg/apiserver-gen/go"
    14  	"github.com/redhat-developer/odo/pkg/apiserver-impl/sse"
    15  	"github.com/redhat-developer/odo/pkg/informer"
    16  	"github.com/redhat-developer/odo/pkg/kclient"
    17  	"github.com/redhat-developer/odo/pkg/log"
    18  	"github.com/redhat-developer/odo/pkg/odo/cli/feature"
    19  	"github.com/redhat-developer/odo/pkg/podman"
    20  	"github.com/redhat-developer/odo/pkg/preference"
    21  	"github.com/redhat-developer/odo/pkg/state"
    22  	"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
    23  	"github.com/redhat-developer/odo/pkg/util"
    24  )
    25  
    26  //go:embed ui/*
    27  var staticFiles embed.FS
    28  
    29  // swagger UI files are from https://github.com/swagger-api/swagger-ui/tree/master/dist
    30  
    31  //go:embed swagger-ui/*
    32  var swaggerFiles embed.FS
    33  
    34  type ApiServer struct {
    35  	PushWatcher <-chan struct{}
    36  }
    37  
    38  func StartServer(
    39  	ctx context.Context,
    40  	cancelFunc context.CancelFunc,
    41  	randomPort bool,
    42  	port int,
    43  	devfilePath string,
    44  	devfileFiles []string,
    45  	fsys filesystem.Filesystem,
    46  	kubernetesClient kclient.ClientInterface,
    47  	podmanClient podman.Client,
    48  	stateClient state.Client,
    49  	preferenceClient preference.Client,
    50  	informerClient *informer.InformerClient,
    51  ) (ApiServer, error) {
    52  	pushWatcher := make(chan struct{})
    53  	defaultApiService := NewDefaultApiService(
    54  		cancelFunc,
    55  		pushWatcher,
    56  		kubernetesClient,
    57  		podmanClient,
    58  		stateClient,
    59  		preferenceClient,
    60  	)
    61  	defaultApiController := openapi.NewDefaultApiController(defaultApiService)
    62  	devstateApiService := NewDevstateApiService(
    63  		cancelFunc,
    64  		pushWatcher,
    65  		kubernetesClient,
    66  		podmanClient,
    67  		stateClient,
    68  		preferenceClient,
    69  	)
    70  	devstateApiController := openapi.NewDevstateApiController(devstateApiService)
    71  
    72  	sseNotifier, err := sse.NewNotifier(ctx, fsys, devfilePath, devfileFiles)
    73  	if err != nil {
    74  		return ApiServer{}, err
    75  	}
    76  
    77  	router := openapi.NewRouter(sseNotifier, defaultApiController, devstateApiController)
    78  
    79  	fSysSwagger, err := fs.Sub(swaggerFiles, "swagger-ui")
    80  	if err != nil {
    81  		// Assertion, error can only happen if the path "swagger-ui" is not valid
    82  		panic(err)
    83  	}
    84  	swaggerServer := http.FileServer(http.FS(fSysSwagger))
    85  	router.PathPrefix("/swagger-ui/").Handler(http.StripPrefix("/swagger-ui/", swaggerServer))
    86  
    87  	if feature.IsEnabled(ctx, feature.UIServer) {
    88  		var fSys fs.FS
    89  		fSys, err = fs.Sub(staticFiles, "ui")
    90  		if err != nil {
    91  			// Assertion, error can only happen if the path "ui" is not valid
    92  			panic(err)
    93  		}
    94  
    95  		staticServer := http.FileServer(http.FS(fSys))
    96  		router.PathPrefix("/").Handler(staticServer)
    97  	}
    98  
    99  	addr := "127.0.0.1"
   100  	if port == 0 && !randomPort {
   101  		port, err = util.NextFreePort(20000, 30001, nil, addr)
   102  		if err != nil {
   103  			klog.V(0).Infof("Unable to start the API server; encountered error: %v", err)
   104  			cancelFunc()
   105  		}
   106  	}
   107  
   108  	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port))
   109  	if err != nil {
   110  		return ApiServer{}, fmt.Errorf("unable to start API Server listener on port %d: %w", port, err)
   111  	}
   112  
   113  	server := &http.Server{
   114  		BaseContext: func(net.Listener) context.Context {
   115  			return ctx
   116  		},
   117  		Handler: router,
   118  	}
   119  	var errChan = make(chan error)
   120  	go func() {
   121  		errChan <- server.Serve(listener)
   122  	}()
   123  
   124  	// Get the actual port value assigned by the operating system
   125  	listeningPort := listener.Addr().(*net.TCPAddr).Port
   126  	if port != 0 && port != listeningPort {
   127  		panic(fmt.Sprintf("requested port (%d) not the same as the actual port the API Server is bound to (%d)", port, listeningPort))
   128  	}
   129  
   130  	err = stateClient.SetAPIServerPort(ctx, listeningPort)
   131  	if err != nil {
   132  		klog.V(0).Infof("Unable to start the API server; encountered error: %v", err)
   133  		cancelFunc()
   134  	}
   135  
   136  	if feature.IsEnabled(ctx, feature.UIServer) {
   137  		info := fmt.Sprintf("Web console accessible at http://localhost:%d/", listeningPort)
   138  		log.Spinner(info).End(true)
   139  		informerClient.AppendInfo(info + "\n")
   140  	}
   141  	log.Spinner(fmt.Sprintf("API Server started at http://localhost:%d/api/v1", listeningPort)).End(true)
   142  	log.Spinner(fmt.Sprintf("API documentation accessible at http://localhost:%d/swagger-ui/", listeningPort)).End(true)
   143  
   144  	go func() {
   145  		select {
   146  		case <-ctx.Done():
   147  			klog.V(0).Infof("Shutting down the API server: %v", ctx.Err())
   148  			err = server.Shutdown(ctx)
   149  			if err != nil {
   150  				klog.V(1).Infof("Error while shutting down the API server: %v", err)
   151  			}
   152  		case err = <-errChan:
   153  			klog.V(0).Infof("Stopping the API server; encountered error: %v", err)
   154  			cancelFunc()
   155  		}
   156  	}()
   157  
   158  	return ApiServer{
   159  		PushWatcher: pushWatcher,
   160  	}, nil
   161  }
   162  

View as plain text