Add gitlab issue/merge request templates
[src/xds/xds-server.git] / lib / xdsserver / sdks.go
index 1a40ab5..1accc60 100644 (file)
@@ -1,3 +1,20 @@
+/*
+ * Copyright (C) 2017-2018 "IoT.bzh"
+ * Author Sebastien Douheret <sebastien@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package xdsserver
 
 import (
@@ -7,57 +24,216 @@ import (
        "strings"
        "sync"
 
-       common "github.com/iotbzh/xds-common/golib"
-       "github.com/iotbzh/xds-server/lib/xsapiv1"
+       common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git"
+       "gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/lib/xdsconfig"
+       "gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/lib/xsapiv1"
 )
 
 // SDKs List of installed SDK
 type SDKs struct {
        *Context
-       Sdks map[string]*CrossSDK
+       Sdks         map[string]*CrossSDK
+       SdksFamilies map[string]*xsapiv1.SDKFamilyConfig
 
        mutex sync.Mutex
+       stop  chan struct{} // signals intentional stop
 }
 
-// NewSDKs creates a new instance of SDKs
-func NewSDKs(ctx *Context) (*SDKs, error) {
+// SDKsConstructor creates a new instance of SDKs
+func SDKsConstructor(ctx *Context) (*SDKs, error) {
        s := SDKs{
-               Context: ctx,
-               Sdks:    make(map[string]*CrossSDK),
+               Context:      ctx,
+               Sdks:         make(map[string]*CrossSDK),
+               SdksFamilies: make(map[string]*xsapiv1.SDKFamilyConfig),
+               stop:         make(chan struct{}),
+       }
+
+       scriptsDir := ctx.Config.FileConf.SdkScriptsDir
+       if !common.Exists(scriptsDir) {
+               // allow to use scripts/sdk when debugging with vscode(EXEPATH=WORKSPACE)
+               scriptsDir = filepath.Join(filepath.Dir(ctx.Config.FileConf.SdkScriptsDir), "scripts", "sdks")
+               if !common.Exists(scriptsDir) {
+                       return &s, fmt.Errorf("scripts directory doesn't exist (%v)", scriptsDir)
+               }
        }
+       s.Log.Infof("SDK scripts dir: %s", scriptsDir)
 
-       // Retrieve installed sdks
-       sdkRD := ctx.Config.FileConf.SdkRootDir
+       dirs, err := filepath.Glob(path.Join(scriptsDir, "*"))
+       if err != nil {
+               s.Log.Errorf("Error while retrieving SDK scripts: dir=%s, error=%s", scriptsDir, err.Error())
+               return &s, err
+       }
 
-       if common.Exists(sdkRD) {
+       // Update SDK DB on startup by default (can be disable using config file)
+       update := true
+       if s.Config.FileConf.SdkDbUpdate != "startup" {
+               update = false
+       }
 
-               // Assume that SDK install tree is <rootdir>/<profile>/<version>/<arch>
-               dirs, err := filepath.Glob(path.Join(sdkRD, "*", "*", "*"))
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
+
+       // Foreach directories in scripts/sdk
+       nbInstalled := 0
+       for _, d := range dirs {
+               if !common.IsDir(d) {
+                       continue
+               }
+
+               sdksList, err := ListCrossSDK(d, update, s.Log)
                if err != nil {
-                       ctx.Log.Debugf("Error while retrieving SDKs: dir=%s, error=%s", sdkRD, err.Error())
-                       return &s, err
+                       // allow to use XDS even if error on list
+                       s.Log.Errorf("Cannot retrieve SDK list: %v", err)
+                       sdksList, _ = ListCrossSDK(d, false, s.Log)
                }
-               s.mutex.Lock()
-               defer s.mutex.Unlock()
+               s.LogSillyf("'%s' SDKs list: %v", d, sdksList)
 
-               for _, d := range dirs {
-                       if !common.IsDir(d) {
-                               continue
-                       }
-                       cSdk, err := NewCrossSDK(d)
+               for _, sdk := range sdksList {
+                       cSdk, err := s._createNewCrossSDK(sdk, d, false, false)
                        if err != nil {
-                               ctx.Log.Debugf("Error while processing SDK dir=%s, err=%s", d, err.Error())
+                               s.Log.Debugf("Error while processing SDK sdk=%v\n err=%s", sdk, err.Error())
                                continue
                        }
-                       s.Sdks[cSdk.sdk.ID] = cSdk
+
+                       if cSdk.sdk.Status == xsapiv1.SdkStatusInstalled {
+                               nbInstalled++
+                       }
+
+                       s.SdksFamilies[cSdk.sdk.FamilyConf.FamilyName] = &cSdk.sdk.FamilyConf
                }
        }
 
-       ctx.Log.Debugf("SDKs: %d cross sdks found", len(s.Sdks))
+       ctx.Log.Infof("Cross SDKs: %d defined, %d installed", len(s.Sdks), nbInstalled)
+
+       // Start monitor thread to detect new SDKs
+       sdksDirs := []string{}
+       for _, sf := range s.SdksFamilies {
+               sdksDirs = append(sdksDirs, sf.RootDir)
+       }
+
+       if len(s.SdksFamilies) == 0 {
+               s.Log.Warningf("No cross SDKs definition found")
+               /* TODO: used it or cleanup
+               } else {
+                       go s.monitorSDKInstallation(sdksDirs)
+               */
+       }
 
        return &s, nil
 }
 
+// _createNewCrossSDK Private function to create a new Cross SDK
+func (s *SDKs) _createNewCrossSDK(sdk xsapiv1.SDK, scriptDir string, installing bool, force bool) (*CrossSDK, error) {
+
+       cSdk, err := NewCrossSDK(s.Context, sdk, scriptDir)
+       if err != nil {
+               return cSdk, err
+       }
+
+       // Allow to overwrite not installed SDK or when force is set
+       if _, exist := s.Sdks[cSdk.sdk.ID]; exist {
+               if !force && cSdk.sdk.Path != "" && common.Exists(cSdk.sdk.Path) {
+                       return cSdk, fmt.Errorf("SDK ID %s already installed in %s", cSdk.sdk.ID, cSdk.sdk.Path)
+               }
+               if !force && cSdk.sdk.Status != xsapiv1.SdkStatusNotInstalled {
+                       return cSdk, fmt.Errorf("Duplicate SDK ID %s (use force to overwrite)", cSdk.sdk.ID)
+               }
+       }
+
+       // Sanity check
+       errMsg := "Invalid SDK definition "
+       if installing && cSdk.sdk.Path == "" {
+               return cSdk, fmt.Errorf(errMsg + "(path not set)")
+       }
+       if installing && cSdk.sdk.URL == "" {
+               return cSdk, fmt.Errorf(errMsg + "(url not set)")
+       }
+
+       // Add to list
+       s.Sdks[cSdk.sdk.ID] = cSdk
+
+       return cSdk, err
+}
+
+// Stop SDKs management
+func (s *SDKs) Stop() {
+       close(s.stop)
+}
+
+// monitorSDKInstallation
+/* TODO: used it or cleanup
+import         "github.com/zillode/notify"
+
+func (s *SDKs) monitorSDKInstallation(watchingDirs []string) {
+
+       // Set up a watchpoint listening for inotify-specific events
+       c := make(chan notify.EventInfo, 1)
+
+       addWatcher := func(rootDir string) error {
+               s.Log.Debugf("SDK Register watcher: rootDir=%s", rootDir)
+
+               if err := notify.Watch(rootDir+"/...", c, notify.Create, notify.Remove); err != nil {
+                       return fmt.Errorf("SDK monitor: rootDir=%v err=%v", rootDir, err)
+               }
+               return nil
+       }
+
+       // Add directory watchers
+       for _, dir := range watchingDirs {
+               if err := addWatcher(dir); err != nil {
+                       s.Log.Errorln(err.Error())
+               }
+       }
+
+       // Wait inotify or stop events
+       for {
+               select {
+               case <-s.stop:
+                       s.Log.Debugln("Stop monitorSDKInstallation")
+                       notify.Stop(c)
+                       return
+               case ei := <-c:
+                       s.LogSillyf("monitorSDKInstallation SDKs event %v, path %v\n", ei.Event(), ei.Path())
+
+                       // Filter out all event that doesn't match environment file
+                       if !strings.Contains(ei.Path(), "environment-setup-") {
+                               continue
+                       }
+                       dir := path.Dir(ei.Path())
+
+                       sdk, err := s.GetByPath(dir)
+                       if err != nil {
+                               s.Log.Warningf("Cannot find SDK path to notify creation")
+                               s.LogSillyf("event: %v", ei.Event())
+                               continue
+                       }
+
+                       switch ei.Event() {
+                       case notify.Create:
+                               sdkDef, err := GetSDKInfo(scriptDir, sdk.URL, "", "", s.Log)
+                               if err != nil {
+                                       s.Log.Warningf("Cannot get sdk info: %v", err)
+                                       continue
+                               }
+                               sdk.Path = sdkDef.Path
+                               sdk.Path = sdkDef.SetupFile
+
+                               // Emit Folder state change event
+                               if err := s.events.Emit(xsapiv1.EVTSDKAdd, sdk, ""); err != nil {
+                                       s.Log.Warningf("Cannot notify SDK install: %v", err)
+                               }
+
+                       case notify.Remove, notify.InMovedFrom:
+                               // Emit Folder state change event
+                               if err := s.events.Emit(xsapiv1.EVTSDKRemove, sdk, ""); err != nil {
+                                       s.Log.Warningf("Cannot notify SDK remove: %v", err)
+                               }
+                       }
+               }
+       }
+}
+*/
+
 // ResolveID Complete an SDK ID (helper for user that can use partial ID value)
 func (s *SDKs) ResolveID(id string) (string, error) {
        if id == "" {
@@ -76,7 +252,7 @@ func (s *SDKs) ResolveID(id string) (string, error) {
        } else if len(match) == 0 {
                return id, fmt.Errorf("Unknown sdk id")
        }
-       return id, fmt.Errorf("Multiple sdk IDs found with provided prefix: " + id)
+       return id, fmt.Errorf("Multiple sdk IDs found: %v", match)
 }
 
 // Get returns an SDK from id
@@ -91,6 +267,19 @@ func (s *SDKs) Get(id string) *xsapiv1.SDK {
        return (*sc).Get()
 }
 
+// GetByPath Find a SDK from path
+func (s *SDKs) GetByPath(path string) (*xsapiv1.SDK, error) {
+       if path == "" {
+               return nil, fmt.Errorf("can't found sdk (empty path)")
+       }
+       for _, ss := range s.Sdks {
+               if ss.sdk.Path == path {
+                       return ss.Get(), nil
+               }
+       }
+       return nil, fmt.Errorf("not found")
+}
+
 // GetAll returns all existing SDKs
 func (s *SDKs) GetAll() []xsapiv1.SDK {
        s.mutex.Lock()
@@ -125,3 +314,122 @@ func (s *SDKs) GetEnvCmd(id string, defaultID string) []string {
        // Return default env that may be empty
        return []string{}
 }
+
+// Install Used to install a new SDK
+func (s *SDKs) Install(id, filepath string, force bool, timeout int, args []string, sess *ClientSession) (*xsapiv1.SDK, error) {
+
+       var sdk *xsapiv1.SDK
+       var err error
+       scriptDir := ""
+       sdkFilename := ""
+
+       if id != "" && filepath != "" {
+               return nil, fmt.Errorf("invalid parameter, both id and filepath are set")
+       }
+
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
+
+       if id != "" {
+               curSdk, exist := s.Sdks[id]
+               if !exist {
+                       return nil, fmt.Errorf("unknown id")
+               }
+
+               sdk = &curSdk.sdk
+               scriptDir = sdk.FamilyConf.ScriptsDir
+
+               // Update path when not set
+               if sdk.Path == "" {
+                       sdkDef, err := GetSDKInfo(scriptDir, sdk.URL, "", "", sdk.UUID, s.Log)
+                       if err != nil || sdkDef.Path == "" {
+                               return nil, fmt.Errorf("cannot retrieve sdk path %v", err)
+                       }
+                       sdk.Path = sdkDef.Path
+               }
+
+       } else if filepath != "" {
+               // FIXME support any location and also sharing either by pathmap or Syncthing
+
+               baseDir := path.Join(xdsconfig.WorkspaceRootDir(), "sdks")
+               sdkFilename, _ = common.ResolveEnvVar(path.Join(baseDir, path.Base(filepath)))
+               if !common.Exists(sdkFilename) {
+                       return nil, fmt.Errorf("SDK file not accessible, must be in %s", baseDir)
+               }
+
+               for _, sf := range s.SdksFamilies {
+                       sdkDef, err := GetSDKInfo(sf.ScriptsDir, "", sdkFilename, "", "", s.Log)
+                       if err == nil {
+                               // OK, sdk found
+                               sdk = &sdkDef
+                               scriptDir = sf.ScriptsDir
+                               break
+                       }
+
+                       s.Log.Debugf("GetSDKInfo error: family=%s, sdkFilename=%s, err=%v", sf.FamilyName, path.Base(sdkFilename), err)
+               }
+               if sdk == nil {
+                       return nil, fmt.Errorf("Cannot identify SDK family for %s", path.Base(filepath))
+               }
+
+       } else {
+               return nil, fmt.Errorf("invalid parameter, id or filepath must be set")
+       }
+
+       cSdk, err := s._createNewCrossSDK(*sdk, scriptDir, true, force)
+       if err != nil {
+               return nil, err
+       }
+
+       // Launch script to install
+       // (note that add event will be generated by monitoring thread)
+       if err := cSdk.Install(sdkFilename, force, timeout, args, sess); err != nil {
+               return &cSdk.sdk, err
+       }
+
+       return &cSdk.sdk, nil
+}
+
+// AbortInstall Used to abort SDK installation
+func (s *SDKs) AbortInstall(id string, timeout int) (*xsapiv1.SDK, error) {
+
+       if id == "" {
+               return nil, fmt.Errorf("invalid parameter")
+       }
+       cSdk, exist := s.Sdks[id]
+       if !exist {
+               return nil, fmt.Errorf("unknown id")
+       }
+
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
+
+       err := cSdk.AbortInstallRemove(timeout)
+
+       return &cSdk.sdk, err
+}
+
+// Remove Used to uninstall a SDK
+func (s *SDKs) Remove(id string, timeout int, sess *ClientSession) (*xsapiv1.SDK, error) {
+
+       cSdk, exist := s.Sdks[id]
+       if !exist {
+               return nil, fmt.Errorf("unknown id")
+       }
+
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
+
+       // Launch script to remove/uninstall
+       // (note that remove event will be generated by monitoring thread)
+       if err := cSdk.Remove(timeout, sess); err != nil {
+               return &cSdk.sdk, err
+       }
+
+       sdk := cSdk.sdk
+
+       // Don't delete it from s.Sdks
+       // (always keep sdk reference to allow for example re-install)
+
+       return &sdk, nil
+}