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
27 var staticFiles embed.FS
28
29
30
31
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
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
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
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