Use go module as dependency tool instead of glide
[src/xds/xds-server.git] / lib / xdsserver / sdks.go
1 /*
2  * Copyright (C) 2017-2018 "IoT.bzh"
3  * Author Sebastien Douheret <sebastien@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package xdsserver
19
20 import (
21         "fmt"
22         "path"
23         "path/filepath"
24         "strings"
25         "sync"
26
27         common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git"
28         "gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/lib/xdsconfig"
29         "gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/lib/xsapiv1"
30 )
31
32 // SDKs List of installed SDK
33 type SDKs struct {
34         *Context
35         Sdks         map[string]*CrossSDK
36         SdksFamilies map[string]*xsapiv1.SDKFamilyConfig
37
38         mutex sync.Mutex
39         stop  chan struct{} // signals intentional stop
40 }
41
42 // SDKsConstructor creates a new instance of SDKs
43 func SDKsConstructor(ctx *Context) (*SDKs, error) {
44         s := SDKs{
45                 Context:      ctx,
46                 Sdks:         make(map[string]*CrossSDK),
47                 SdksFamilies: make(map[string]*xsapiv1.SDKFamilyConfig),
48                 stop:         make(chan struct{}),
49         }
50
51         scriptsDir := ctx.Config.FileConf.SdkScriptsDir
52         if !common.Exists(scriptsDir) {
53                 // allow to use scripts/sdk when debugging with vscode(EXEPATH=WORKSPACE)
54                 scriptsDir = filepath.Join(filepath.Dir(ctx.Config.FileConf.SdkScriptsDir), "scripts", "sdks")
55                 if !common.Exists(scriptsDir) {
56                         return &s, fmt.Errorf("scripts directory doesn't exist (%v)", scriptsDir)
57                 }
58         }
59         s.Log.Infof("SDK scripts dir: %s", scriptsDir)
60
61         dirs, err := filepath.Glob(path.Join(scriptsDir, "*"))
62         if err != nil {
63                 s.Log.Errorf("Error while retrieving SDK scripts: dir=%s, error=%s", scriptsDir, err.Error())
64                 return &s, err
65         }
66
67         // Update SDK DB on startup by default (can be disable using config file)
68         update := true
69         if s.Config.FileConf.SdkDbUpdate != "startup" {
70                 update = false
71         }
72
73         s.mutex.Lock()
74         defer s.mutex.Unlock()
75
76         // Foreach directories in scripts/sdk
77         nbInstalled := 0
78         for _, d := range dirs {
79                 if !common.IsDir(d) {
80                         continue
81                 }
82
83                 sdksList, err := ListCrossSDK(d, update, s.Log)
84                 if err != nil {
85                         // allow to use XDS even if error on list
86                         s.Log.Errorf("Cannot retrieve SDK list: %v", err)
87                         sdksList, _ = ListCrossSDK(d, false, s.Log)
88                 }
89                 s.LogSillyf("'%s' SDKs list: %v", d, sdksList)
90
91                 for _, sdk := range sdksList {
92                         cSdk, err := s._createNewCrossSDK(sdk, d, false, false)
93                         if err != nil {
94                                 s.Log.Debugf("Error while processing SDK sdk=%v\n err=%s", sdk, err.Error())
95                                 continue
96                         }
97
98                         if cSdk.sdk.Status == xsapiv1.SdkStatusInstalled {
99                                 nbInstalled++
100                         }
101
102                         s.SdksFamilies[cSdk.sdk.FamilyConf.FamilyName] = &cSdk.sdk.FamilyConf
103                 }
104         }
105
106         ctx.Log.Infof("Cross SDKs: %d defined, %d installed", len(s.Sdks), nbInstalled)
107
108         // Start monitor thread to detect new SDKs
109         sdksDirs := []string{}
110         for _, sf := range s.SdksFamilies {
111                 sdksDirs = append(sdksDirs, sf.RootDir)
112         }
113
114         if len(s.SdksFamilies) == 0 {
115                 s.Log.Warningf("No cross SDKs definition found")
116                 /* TODO: used it or cleanup
117                 } else {
118                         go s.monitorSDKInstallation(sdksDirs)
119                 */
120         }
121
122         return &s, nil
123 }
124
125 // _createNewCrossSDK Private function to create a new Cross SDK
126 func (s *SDKs) _createNewCrossSDK(sdk xsapiv1.SDK, scriptDir string, installing bool, force bool) (*CrossSDK, error) {
127
128         cSdk, err := NewCrossSDK(s.Context, sdk, scriptDir)
129         if err != nil {
130                 return cSdk, err
131         }
132
133         // Allow to overwrite not installed SDK or when force is set
134         if _, exist := s.Sdks[cSdk.sdk.ID]; exist {
135                 if !force && cSdk.sdk.Path != "" && common.Exists(cSdk.sdk.Path) {
136                         return cSdk, fmt.Errorf("SDK ID %s already installed in %s", cSdk.sdk.ID, cSdk.sdk.Path)
137                 }
138                 if !force && cSdk.sdk.Status != xsapiv1.SdkStatusNotInstalled {
139                         return cSdk, fmt.Errorf("Duplicate SDK ID %s (use force to overwrite)", cSdk.sdk.ID)
140                 }
141         }
142
143         // Sanity check
144         errMsg := "Invalid SDK definition "
145         if installing && cSdk.sdk.Path == "" {
146                 return cSdk, fmt.Errorf(errMsg + "(path not set)")
147         }
148         if installing && cSdk.sdk.URL == "" {
149                 return cSdk, fmt.Errorf(errMsg + "(url not set)")
150         }
151
152         // Add to list
153         s.Sdks[cSdk.sdk.ID] = cSdk
154
155         return cSdk, err
156 }
157
158 // Stop SDKs management
159 func (s *SDKs) Stop() {
160         close(s.stop)
161 }
162
163 // monitorSDKInstallation
164 /* TODO: used it or cleanup
165 import  "github.com/zillode/notify"
166
167 func (s *SDKs) monitorSDKInstallation(watchingDirs []string) {
168
169         // Set up a watchpoint listening for inotify-specific events
170         c := make(chan notify.EventInfo, 1)
171
172         addWatcher := func(rootDir string) error {
173                 s.Log.Debugf("SDK Register watcher: rootDir=%s", rootDir)
174
175                 if err := notify.Watch(rootDir+"/...", c, notify.Create, notify.Remove); err != nil {
176                         return fmt.Errorf("SDK monitor: rootDir=%v err=%v", rootDir, err)
177                 }
178                 return nil
179         }
180
181         // Add directory watchers
182         for _, dir := range watchingDirs {
183                 if err := addWatcher(dir); err != nil {
184                         s.Log.Errorln(err.Error())
185                 }
186         }
187
188         // Wait inotify or stop events
189         for {
190                 select {
191                 case <-s.stop:
192                         s.Log.Debugln("Stop monitorSDKInstallation")
193                         notify.Stop(c)
194                         return
195                 case ei := <-c:
196                         s.LogSillyf("monitorSDKInstallation SDKs event %v, path %v\n", ei.Event(), ei.Path())
197
198                         // Filter out all event that doesn't match environment file
199                         if !strings.Contains(ei.Path(), "environment-setup-") {
200                                 continue
201                         }
202                         dir := path.Dir(ei.Path())
203
204                         sdk, err := s.GetByPath(dir)
205                         if err != nil {
206                                 s.Log.Warningf("Cannot find SDK path to notify creation")
207                                 s.LogSillyf("event: %v", ei.Event())
208                                 continue
209                         }
210
211                         switch ei.Event() {
212                         case notify.Create:
213                                 sdkDef, err := GetSDKInfo(scriptDir, sdk.URL, "", "", s.Log)
214                                 if err != nil {
215                                         s.Log.Warningf("Cannot get sdk info: %v", err)
216                                         continue
217                                 }
218                                 sdk.Path = sdkDef.Path
219                                 sdk.Path = sdkDef.SetupFile
220
221                                 // Emit Folder state change event
222                                 if err := s.events.Emit(xsapiv1.EVTSDKAdd, sdk, ""); err != nil {
223                                         s.Log.Warningf("Cannot notify SDK install: %v", err)
224                                 }
225
226                         case notify.Remove, notify.InMovedFrom:
227                                 // Emit Folder state change event
228                                 if err := s.events.Emit(xsapiv1.EVTSDKRemove, sdk, ""); err != nil {
229                                         s.Log.Warningf("Cannot notify SDK remove: %v", err)
230                                 }
231                         }
232                 }
233         }
234 }
235 */
236
237 // ResolveID Complete an SDK ID (helper for user that can use partial ID value)
238 func (s *SDKs) ResolveID(id string) (string, error) {
239         if id == "" {
240                 return "", nil
241         }
242
243         match := []string{}
244         for iid := range s.Sdks {
245                 if strings.HasPrefix(iid, id) {
246                         match = append(match, iid)
247                 }
248         }
249
250         if len(match) == 1 {
251                 return match[0], nil
252         } else if len(match) == 0 {
253                 return id, fmt.Errorf("Unknown sdk id")
254         }
255         return id, fmt.Errorf("Multiple sdk IDs found: %v", match)
256 }
257
258 // Get returns an SDK from id
259 func (s *SDKs) Get(id string) *xsapiv1.SDK {
260         s.mutex.Lock()
261         defer s.mutex.Unlock()
262
263         sc, exist := s.Sdks[id]
264         if !exist {
265                 return nil
266         }
267         return (*sc).Get()
268 }
269
270 // GetByPath Find a SDK from path
271 func (s *SDKs) GetByPath(path string) (*xsapiv1.SDK, error) {
272         if path == "" {
273                 return nil, fmt.Errorf("can't found sdk (empty path)")
274         }
275         for _, ss := range s.Sdks {
276                 if ss.sdk.Path == path {
277                         return ss.Get(), nil
278                 }
279         }
280         return nil, fmt.Errorf("not found")
281 }
282
283 // GetAll returns all existing SDKs
284 func (s *SDKs) GetAll() []xsapiv1.SDK {
285         s.mutex.Lock()
286         defer s.mutex.Unlock()
287         res := []xsapiv1.SDK{}
288         for _, v := range s.Sdks {
289                 res = append(res, *(*v).Get())
290         }
291         return res
292 }
293
294 // GetEnvCmd returns the command used to initialized the environment for an SDK
295 func (s *SDKs) GetEnvCmd(id string, defaultID string) []string {
296         if id == "" && defaultID == "" {
297                 // no env cmd
298                 return []string{}
299         }
300
301         s.mutex.Lock()
302         defer s.mutex.Unlock()
303
304         if iid, err := s.ResolveID(id); err == nil {
305                 if sdk, exist := s.Sdks[iid]; exist {
306                         return sdk.GetEnvCmd()
307                 }
308         }
309
310         if sdk, exist := s.Sdks[defaultID]; defaultID != "" && exist {
311                 return sdk.GetEnvCmd()
312         }
313
314         // Return default env that may be empty
315         return []string{}
316 }
317
318 // Install Used to install a new SDK
319 func (s *SDKs) Install(id, filepath string, force bool, timeout int, args []string, sess *ClientSession) (*xsapiv1.SDK, error) {
320
321         var sdk *xsapiv1.SDK
322         var err error
323         scriptDir := ""
324         sdkFilename := ""
325
326         if id != "" && filepath != "" {
327                 return nil, fmt.Errorf("invalid parameter, both id and filepath are set")
328         }
329
330         s.mutex.Lock()
331         defer s.mutex.Unlock()
332
333         if id != "" {
334                 curSdk, exist := s.Sdks[id]
335                 if !exist {
336                         return nil, fmt.Errorf("unknown id")
337                 }
338
339                 sdk = &curSdk.sdk
340                 scriptDir = sdk.FamilyConf.ScriptsDir
341
342                 // Update path when not set
343                 if sdk.Path == "" {
344                         sdkDef, err := GetSDKInfo(scriptDir, sdk.URL, "", "", sdk.UUID, s.Log)
345                         if err != nil || sdkDef.Path == "" {
346                                 return nil, fmt.Errorf("cannot retrieve sdk path %v", err)
347                         }
348                         sdk.Path = sdkDef.Path
349                 }
350
351         } else if filepath != "" {
352                 // FIXME support any location and also sharing either by pathmap or Syncthing
353
354                 baseDir := path.Join(xdsconfig.WorkspaceRootDir(), "sdks")
355                 sdkFilename, _ = common.ResolveEnvVar(path.Join(baseDir, path.Base(filepath)))
356                 if !common.Exists(sdkFilename) {
357                         return nil, fmt.Errorf("SDK file not accessible, must be in %s", baseDir)
358                 }
359
360                 for _, sf := range s.SdksFamilies {
361                         sdkDef, err := GetSDKInfo(sf.ScriptsDir, "", sdkFilename, "", "", s.Log)
362                         if err == nil {
363                                 // OK, sdk found
364                                 sdk = &sdkDef
365                                 scriptDir = sf.ScriptsDir
366                                 break
367                         }
368
369                         s.Log.Debugf("GetSDKInfo error: family=%s, sdkFilename=%s, err=%v", sf.FamilyName, path.Base(sdkFilename), err)
370                 }
371                 if sdk == nil {
372                         return nil, fmt.Errorf("Cannot identify SDK family for %s", path.Base(filepath))
373                 }
374
375         } else {
376                 return nil, fmt.Errorf("invalid parameter, id or filepath must be set")
377         }
378
379         cSdk, err := s._createNewCrossSDK(*sdk, scriptDir, true, force)
380         if err != nil {
381                 return nil, err
382         }
383
384         // Launch script to install
385         // (note that add event will be generated by monitoring thread)
386         if err := cSdk.Install(sdkFilename, force, timeout, args, sess); err != nil {
387                 return &cSdk.sdk, err
388         }
389
390         return &cSdk.sdk, nil
391 }
392
393 // AbortInstall Used to abort SDK installation
394 func (s *SDKs) AbortInstall(id string, timeout int) (*xsapiv1.SDK, error) {
395
396         if id == "" {
397                 return nil, fmt.Errorf("invalid parameter")
398         }
399         cSdk, exist := s.Sdks[id]
400         if !exist {
401                 return nil, fmt.Errorf("unknown id")
402         }
403
404         s.mutex.Lock()
405         defer s.mutex.Unlock()
406
407         err := cSdk.AbortInstallRemove(timeout)
408
409         return &cSdk.sdk, err
410 }
411
412 // Remove Used to uninstall a SDK
413 func (s *SDKs) Remove(id string, timeout int, sess *ClientSession) (*xsapiv1.SDK, error) {
414
415         cSdk, exist := s.Sdks[id]
416         if !exist {
417                 return nil, fmt.Errorf("unknown id")
418         }
419
420         s.mutex.Lock()
421         defer s.mutex.Unlock()
422
423         // Launch script to remove/uninstall
424         // (note that remove event will be generated by monitoring thread)
425         if err := cSdk.Remove(timeout, sess); err != nil {
426                 return &cSdk.sdk, err
427         }
428
429         sdk := cSdk.sdk
430
431         // Don't delete it from s.Sdks
432         // (always keep sdk reference to allow for example re-install)
433
434         return &sdk, nil
435 }