/* * Copyright (C) 2017-2018 "IoT.bzh" * Author Sebastien Douheret * * 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 ( "fmt" "path" "path/filepath" "strings" "sync" common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib" "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xdsconfig" "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1" ) // SDKs List of installed SDK type SDKs struct { *Context Sdks map[string]*CrossSDK SdksFamilies map[string]*xsapiv1.SDKFamilyConfig mutex sync.Mutex stop chan struct{} // signals intentional stop } // SDKsConstructor creates a new instance of SDKs func SDKsConstructor(ctx *Context) (*SDKs, error) { s := SDKs{ 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 in debug mode 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) 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 } // Update SDK DB on startup by default (can be disable using config file) update := true if s.Config.FileConf.SdkDbUpdate != "startup" { update = false } 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 { // 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.LogSillyf("'%s' SDKs list: %v", d, sdksList) for _, sdk := range sdksList { cSdk, err := s._createNewCrossSDK(sdk, d, false, false) if err != nil { s.Log.Debugf("Error while processing SDK sdk=%v\n err=%s", sdk, err.Error()) continue } if cSdk.sdk.Status == xsapiv1.SdkStatusInstalled { nbInstalled++ } s.SdksFamilies[cSdk.sdk.FamilyConf.FamilyName] = &cSdk.sdk.FamilyConf } } ctx.Log.Debugf("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 == "" { return "", nil } match := []string{} for iid := range s.Sdks { if strings.HasPrefix(iid, id) { match = append(match, iid) } } if len(match) == 1 { return match[0], nil } else if len(match) == 0 { return id, fmt.Errorf("Unknown sdk id") } return id, fmt.Errorf("Multiple sdk IDs found: %v", match) } // Get returns an SDK from id func (s *SDKs) Get(id string) *xsapiv1.SDK { s.mutex.Lock() defer s.mutex.Unlock() sc, exist := s.Sdks[id] if !exist { return nil } 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() defer s.mutex.Unlock() res := []xsapiv1.SDK{} for _, v := range s.Sdks { res = append(res, *(*v).Get()) } return res } // GetEnvCmd returns the command used to initialized the environment for an SDK func (s *SDKs) GetEnvCmd(id string, defaultID string) []string { if id == "" && defaultID == "" { // no env cmd return []string{} } s.mutex.Lock() defer s.mutex.Unlock() if iid, err := s.ResolveID(id); err == nil { if sdk, exist := s.Sdks[iid]; exist { return sdk.GetEnvCmd() } } if sdk, exist := s.Sdks[defaultID]; defaultID != "" && exist { return sdk.GetEnvCmd() } // 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 }