Added SDKs management support.
authorSebastien Douheret <sebastien.douheret@iot.bzh>
Fri, 22 Dec 2017 20:26:40 +0000 (21:26 +0100)
committerSebastien Douheret <sebastien.douheret@iot.bzh>
Fri, 22 Dec 2017 20:29:59 +0000 (21:29 +0100)
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
26 files changed:
.gitignore
.vscode/launch.json
.vscode/settings.json
Makefile
conf.d/etc/xds/server/server-config.json
lib/syncthing/st.go
lib/xdsconfig/config.go
lib/xdsconfig/fileconfig.go
lib/xdsserver/apiv1-events.go
lib/xdsserver/apiv1-sdks.go
lib/xdsserver/apiv1.go
lib/xdsserver/sdk.go
lib/xdsserver/sdks.go
lib/xdsserver/webserver.go
lib/xsapiv1/events.go
lib/xsapiv1/sdks.go
scripts/sdks/README.md [new file with mode: 0644]
scripts/sdks/agl/_build-sdks-json.sh [new file with mode: 0755]
scripts/sdks/agl/_env-init.sh [new file with mode: 0755]
scripts/sdks/agl/add [new file with mode: 0755]
scripts/sdks/agl/get-config [new file with mode: 0755]
scripts/sdks/agl/list [new file with mode: 0755]
scripts/sdks/agl/remove [new file with mode: 0755]
scripts/sdks/agl/update [new file with mode: 0755]
scripts/xds-utils/install-agl-sdks.sh
webapp/src/index.html

index a5007eb..debbc5d 100644 (file)
@@ -6,6 +6,7 @@ package
 debug
 cmd/*/debug
 *.zip
+scripts/sdks/**/sdks*.json
 
 webapp/dist
 webapp/node_modules
index 245c2ee..f976fb6 100644 (file)
             },
             "args": ["-log", "debug", "-c", "__config_local_dev.json"],
             "showLog": false
+        },
+        {
+            "type": "node",
+            "request": "launch",
+            "name": "Script sdk list",
+            "program": "${workspaceFolder}/scripts/sdks/agl/list"
         }
     ]
 }
index 24aa012..6415d9e 100644 (file)
@@ -29,6 +29,7 @@
         "inotify", "Inot", "pname", "pkill", "sdkid", "CLOUDSYNC", "xdsagent",
         "gdbserver", "golib", "eows", "mfolders", "IFOLDER", "flds", "dflt",
         "stconfig", "reflectme", "franciscocpg", "crosssdk", "urfave", "EXEPATH",
-        "conv", "Sillyf", "xsapiv"
+        "conv", "Sillyf", "xsapiv",
+        "EVTSDK"
     ]
 }
index 86107f1..adefb65 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -149,7 +149,7 @@ webapp/install:
 
 .PHONY: scripts
 scripts:
-       @mkdir -p $(LOCAL_BINDIR) && cp -rf scripts/xds-utils $(LOCAL_BINDIR)
+       @mkdir -p $(LOCAL_BINDIR) && cp -rf scripts/xds-utils scripts/sdks $(LOCAL_BINDIR)
 
 .PHONY: conffile
 conffile:
index 5562bb3..a27d4f5 100644 (file)
@@ -2,7 +2,7 @@
     "webAppDir": "www",
     "httpPort": "8000",
     "shareRootDir": "${HOME}/.xds/server/projects",
-    "sdkRootDir": "/xdt/sdk",
+    "sdkScriptsDir": "${EXEPATH}/scripts/sdks",
     "syncthing": {
         "binDir": "",
         "home": "${HOME}/.xds/server/syncthing-config",
index a52e1fe..bb50ea8 100644 (file)
@@ -191,7 +191,7 @@ func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan
 
                cmdOut, err := cmd.StdoutPipe()
                if err != nil {
-                       return nil, fmt.Errorf("Pipe stdout error for : %s", err)
+                       return nil, fmt.Errorf("Pipe stdout error for : %v", err)
                }
 
                go io.Copy(outfile, cmdOut)
index 59cf394..74ce21e 100644 (file)
@@ -55,7 +55,7 @@ const (
        DefaultPort       = "8000"
        DefaultShareDir   = "${HOME}/.xds/server/projects"
        DefaultSTHomeDir  = "${HOME}/.xds/server/syncthing-config"
-       DefaultSdkRootDir = "/xdt/sdk"
+       DefaultSdkScriptsDir = "${EXEPATH}/scripts/sdks"
 )
 
 // Init loads the configuration on start-up
@@ -97,7 +97,7 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) {
                FileConf: FileConfig{
                        WebAppDir:    "webapp/dist",
                        ShareRootDir: dfltShareDir,
-                       SdkRootDir:   DefaultSdkRootDir,
+                       SdkScriptsDir: DefaultSdkScriptsDir,
                        HTTPPort:     DefaultPort,
                        SThgConf:     &SyncThingConf{Home: dfltSTHomeDir},
                        LogsDir:      "",
index 8e77de7..bf8aa25 100644 (file)
@@ -51,12 +51,12 @@ type SyncThingConf struct {
 
 // FileConfig is the JSON structure of xds-server config file (server-config.json)
 type FileConfig struct {
-       WebAppDir    string         `json:"webAppDir"`
-       ShareRootDir string         `json:"shareRootDir"`
-       SdkRootDir   string         `json:"sdkRootDir"`
-       HTTPPort     string         `json:"httpPort"`
-       SThgConf     *SyncThingConf `json:"syncthing"`
-       LogsDir      string         `json:"logsDir"`
+       WebAppDir     string         `json:"webAppDir"`
+       ShareRootDir  string         `json:"shareRootDir"`
+       SdkScriptsDir string         `json:"sdkScriptsDir"`
+       HTTPPort      string         `json:"httpPort"`
+       SThgConf      *SyncThingConf `json:"syncthing"`
+       LogsDir       string         `json:"logsDir"`
 }
 
 // readGlobalConfig reads configuration from a config file.
@@ -117,7 +117,7 @@ func readGlobalConfig(c *Config, confFile string) error {
        vars := []*string{
                &fCfg.WebAppDir,
                &fCfg.ShareRootDir,
-               &fCfg.SdkRootDir,
+               &fCfg.SdkScriptsDir,
                &fCfg.LogsDir}
        if fCfg.SThgConf != nil {
                vars = append(vars, &fCfg.SThgConf.Home, &fCfg.SThgConf.BinDir)
@@ -136,8 +136,8 @@ func readGlobalConfig(c *Config, confFile string) error {
        if fCfg.ShareRootDir == "" {
                fCfg.ShareRootDir = c.FileConf.ShareRootDir
        }
-       if fCfg.SdkRootDir == "" {
-               fCfg.SdkRootDir = c.FileConf.SdkRootDir
+       if fCfg.SdkScriptsDir == "" {
+               fCfg.SdkScriptsDir = c.FileConf.SdkScriptsDir
        }
        if fCfg.HTTPPort == "" {
                fCfg.HTTPPort = c.FileConf.HTTPPort
index 0942753..eedd747 100644 (file)
@@ -39,6 +39,8 @@ func (s *APIService) eventsRegister(c *gin.Context) {
                return
        }
 
+       // TODO: add args.Filter support
+
        sess := s.sessions.Get(c)
        if sess == nil {
                common.APIError(c, "Unknown sessions")
index bcb293b..b38c418 100644 (file)
@@ -22,6 +22,7 @@ import (
 
        "github.com/gin-gonic/gin"
        common "github.com/iotbzh/xds-common/golib"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
 )
 
 // getSdks returns all SDKs configuration
@@ -44,3 +45,81 @@ func (s *APIService) getSdk(c *gin.Context) {
 
        c.JSON(http.StatusOK, sdk)
 }
+
+// installSdk Install a new Sdk
+func (s *APIService) installSdk(c *gin.Context) {
+       var args xsapiv1.SDKInstallArgs
+
+       if err := c.BindJSON(&args); err != nil {
+               common.APIError(c, "Invalid arguments")
+               return
+       }
+       id, err := s.sdks.ResolveID(args.ID)
+       if err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       // Support install from ID->URL or from local file
+       if id != "" {
+               s.Log.Debugf("Installing SDK id %s (force %v)", id, args.Force)
+       } else if args.Filename != "" {
+               s.Log.Debugf("Installing SDK filename %s (force %v)", args.Filename, args.Force)
+       }
+
+       // Retrieve session info
+       sess := s.sessions.Get(c)
+       if sess == nil {
+               common.APIError(c, "Unknown sessions")
+               return
+       }
+
+       sdk, err := s.sdks.Install(id, args.Filename, args.Force, args.Timeout, sess)
+       if err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       c.JSON(http.StatusOK, sdk)
+}
+
+// abortInstallSdk Abort a SDK installation
+func (s *APIService) abortInstallSdk(c *gin.Context) {
+       var args xsapiv1.SDKInstallArgs
+
+       if err := c.BindJSON(&args); err != nil {
+               common.APIError(c, "Invalid arguments")
+               return
+       }
+       id, err := s.sdks.ResolveID(args.ID)
+       if err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       sdk, err := s.sdks.AbortInstall(id, args.Timeout)
+       if err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       c.JSON(http.StatusOK, sdk)
+}
+
+// removeSdk Uninstall a Sdk
+func (s *APIService) removeSdk(c *gin.Context) {
+       id, err := s.sdks.ResolveID(c.Param("id"))
+       if err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+
+       s.Log.Debugln("Remove SDK id ", id)
+
+       delEntry, err := s.sdks.Remove(id)
+       if err != nil {
+               common.APIError(c, err.Error())
+               return
+       }
+       c.JSON(http.StatusOK, delEntry)
+}
index 143c25f..f9d5948 100644 (file)
@@ -48,6 +48,9 @@ func NewAPIV1(ctx *Context) *APIService {
 
        s.apiRouter.GET("/sdks", s.getSdks)
        s.apiRouter.GET("/sdks/:id", s.getSdk)
+       s.apiRouter.POST("/sdks", s.installSdk)
+       s.apiRouter.POST("/sdks/abortinstall", s.abortInstallSdk)
+       s.apiRouter.DELETE("/sdks/:id", s.removeSdk)
 
        s.apiRouter.POST("/make", s.buildMake)
        s.apiRouter.POST("/make/:id", s.buildMake)
index 7a90f6b..c011d09 100644 (file)
 package xdsserver
 
 import (
+       "encoding/json"
        "fmt"
-       "path/filepath"
+       "os"
+       "os/exec"
+       "path"
+       "strconv"
+       "strings"
+       "time"
 
+       "github.com/Sirupsen/logrus"
+       common "github.com/iotbzh/xds-common/golib"
+       "github.com/iotbzh/xds-common/golib/eows"
        "github.com/iotbzh/xds-server/lib/xsapiv1"
        uuid "github.com/satori/go.uuid"
 )
 
+// Definition of scripts used to managed SDKs
+const (
+       scriptAdd       = "add"
+       scriptGetConfig = "get-config"
+       scriptList      = "list"
+       scriptRemove    = "remove"
+       scriptUpdate    = "update"
+)
+
+var scriptsAll = []string{
+       scriptAdd,
+       scriptGetConfig,
+       scriptList,
+       scriptRemove,
+       scriptUpdate,
+}
+
+var sdkCmdID = 0
+
 // CrossSDK Hold SDK config
 type CrossSDK struct {
-       sdk xsapiv1.SDK
+       *Context
+       sdk        xsapiv1.SDK
+       scripts    map[string]string
+       installCmd *eows.ExecOverWS
+       removeCmd  *eows.ExecOverWS
+
+       bufStdout string
+       bufStderr string
+}
+
+// ListCrossSDK List all available and installed SDK  (call "list" script)
+func ListCrossSDK(scriptDir string, log *logrus.Logger) ([]xsapiv1.SDK, error) {
+       sdksList := []xsapiv1.SDK{}
+
+       // Retrieve SDKs list and info
+       cmd := exec.Command(path.Join(scriptDir, scriptList))
+       stdout, err := cmd.CombinedOutput()
+       if err != nil {
+               return sdksList, fmt.Errorf("Cannot get sdks list: %v", err)
+       }
+
+       if err = json.Unmarshal(stdout, &sdksList); err != nil {
+               log.Errorf("SDK list script output:\n%v\n", string(stdout))
+               return sdksList, fmt.Errorf("Cannot decode sdk list %v", err)
+       }
+
+       return sdksList, nil
 }
 
 // NewCrossSDK creates a new instance of Syncthing
-func NewCrossSDK(path string) (*CrossSDK, error) {
-       // Assume that we have .../<profile>/<version>/<arch>
+func NewCrossSDK(ctx *Context, sdk xsapiv1.SDK, scriptDir string) (*CrossSDK, error) {
        s := CrossSDK{
-               sdk: xsapiv1.SDK{Path: path},
+               Context: ctx,
+               sdk:     sdk,
+               scripts: make(map[string]string),
        }
 
-       s.sdk.Arch = filepath.Base(path)
+       // Execute get-config script to retrieve SDK configuration
+       getConfFile := path.Join(scriptDir, scriptGetConfig)
+       if !common.Exists(getConfFile) {
+               return &s, fmt.Errorf("'%s' script file not found in %s", scriptGetConfig, scriptDir)
+       }
 
-       d := filepath.Dir(path)
-       s.sdk.Version = filepath.Base(d)
+       cmd := exec.Command(getConfFile)
+       stdout, err := cmd.CombinedOutput()
+       if err != nil {
+               return &s, fmt.Errorf("Cannot get sdk config using %s: %v", getConfFile, err)
+       }
 
-       d = filepath.Dir(d)
-       s.sdk.Profile = filepath.Base(d)
+       err = json.Unmarshal(stdout, &s.sdk.FamilyConf)
+       if err != nil {
+               s.Log.Errorf("SDK config script output:\n%v\n", string(stdout))
+               return &s, fmt.Errorf("Cannot decode sdk config %v", err)
+       }
+       famName := s.sdk.FamilyConf.FamilyName
 
-       // Use V3 to ensure that we get same uuid on restart
-       s.sdk.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), s.sdk.Profile+"_"+s.sdk.Arch+"_"+s.sdk.Version).String()
-       s.sdk.Name = s.sdk.Arch + "  (" + s.sdk.Version + ")"
+       // Sanity check
+       if s.sdk.FamilyConf.RootDir == "" {
+               return &s, fmt.Errorf("SDK config not valid (rootDir not set)")
+       }
+       if s.sdk.FamilyConf.EnvSetupFile == "" {
+               return &s, fmt.Errorf("SDK config not valid (envSetupFile not set)")
+       }
 
-       envFile := filepath.Join(path, "environment-setup*")
-       ef, err := filepath.Glob(envFile)
-       if err != nil {
-               return nil, fmt.Errorf("Cannot retrieve environment setup file: %v", err)
+       // Check that other mandatory scripts are present
+       for _, scr := range scriptsAll {
+               s.scripts[scr] = path.Join(scriptDir, scr)
+               if !common.Exists(s.scripts[scr]) {
+                       return &s, fmt.Errorf("Script named '%s' missing in SDK family '%s'", scr, famName)
+               }
+       }
+
+       // Fixed default fields value
+       sdk.LastError = ""
+       if sdk.Status == "" {
+               sdk.Status = xsapiv1.SdkStatusNotInstalled
        }
-       if len(ef) != 1 {
-               return nil, fmt.Errorf("No environment setup file found match %s", envFile)
+
+       // Sanity check
+       errMsg := "Invalid SDK definition "
+       if sdk.Name == "" {
+               return &s, fmt.Errorf(errMsg + "(name not set)")
+       } else if sdk.Profile == "" {
+               return &s, fmt.Errorf(errMsg + "(profile not set)")
+       } else if sdk.Version == "" {
+               return &s, fmt.Errorf(errMsg + "(version not set)")
+       } else if sdk.Arch == "" {
+               return &s, fmt.Errorf(errMsg + "(arch not set)")
+       }
+       if sdk.Status == xsapiv1.SdkStatusInstalled {
+               if sdk.SetupFile == "" {
+                       return &s, fmt.Errorf(errMsg + "(setupFile not set)")
+               } else if !common.Exists(sdk.SetupFile) {
+                       return &s, fmt.Errorf(errMsg + "(setupFile not accessible)")
+               }
+               if sdk.Path == "" {
+                       return &s, fmt.Errorf(errMsg + "(path not set)")
+               } else if !common.Exists(sdk.Path) {
+                       return &s, fmt.Errorf(errMsg + "(path not accessible)")
+               }
+       }
+
+       // Use V3 to ensure that we get same uuid on restart
+       nm := s.sdk.Name
+       if nm == "" {
+               nm = s.sdk.Profile + "_" + s.sdk.Arch + "_" + s.sdk.Version
        }
-       s.sdk.EnvFile = ef[0]
+       s.sdk.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), nm).String()
+
+       s.LogSillyf("New SDK: ID=%v, Family=%s, Name=%v", s.sdk.ID[:8], s.sdk.FamilyConf.FamilyName, s.sdk.Name)
 
        return &s, nil
 }
 
+// Install a SDK (non blocking command, IOW run in background)
+func (s *CrossSDK) Install(file string, force bool, timeout int, sess *ClientSession) error {
+
+       if s.sdk.Status == xsapiv1.SdkStatusInstalled {
+               return fmt.Errorf("already installed")
+       }
+       if s.sdk.Status == xsapiv1.SdkStatusInstalling {
+               return fmt.Errorf("installation in progress")
+       }
+
+       // Compute command args
+       cmdArgs := []string{}
+       if file != "" {
+               cmdArgs = append(cmdArgs, "--file", file)
+       } else {
+               cmdArgs = append(cmdArgs, "--url", s.sdk.URL)
+       }
+       if force {
+               cmdArgs = append(cmdArgs, "--force")
+       }
+
+       // Unique command id
+       sdkCmdID++
+       cmdID := "sdk-install-" + strconv.Itoa(sdkCmdID)
+
+       // Create new instance to execute command and sent output over WS
+       s.installCmd = eows.New(s.scripts[scriptAdd], cmdArgs, sess.IOSocket, sess.ID, cmdID)
+       s.installCmd.Log = s.Log
+       if timeout > 0 {
+               s.installCmd.CmdExecTimeout = timeout
+       } else {
+               s.installCmd.CmdExecTimeout = 30 * 60 // default 30min
+       }
+
+       // FIXME: temporary hack
+       s.bufStdout = ""
+       s.bufStderr = ""
+       SizeBufStdout := 10
+       SizeBufStderr := 2000
+       if valS, ok := os.LookupEnv("XDS_SDK_BUF_STDOUT"); ok {
+               if valI, err := strconv.Atoi(valS); err == nil {
+                       SizeBufStdout = valI
+               }
+       }
+       if valS, ok := os.LookupEnv("XDS_SDK_BUF_STDERR"); ok {
+               if valI, err := strconv.Atoi(valS); err == nil {
+                       SizeBufStderr = valI
+               }
+       }
+
+       // Define callback for output (stdout+stderr)
+       s.installCmd.OutputCB = func(e *eows.ExecOverWS, stdout, stderr string) {
+               // paranoia
+               data := e.UserData
+               sdkID := (*data)["SDKID"].(string)
+               if sdkID != s.sdk.ID {
+                       s.Log.Errorln("BUG: sdk ID differs: %v != %v", sdkID, s.sdk.ID)
+               }
+
+               // IO socket can be nil when disconnected
+               so := s.sessions.IOSocketGet(e.Sid)
+               if so == nil {
+                       s.Log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", xsapiv1.EVTSDKInstall, e.Sid, e.CmdID)
+                       return
+               }
+
+               if s.LogLevelSilly {
+                       s.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - SDK ID:%s:", xsapiv1.EVTSDKInstall, e.Sid[4:], e.CmdID, sdkID[:16])
+                       if stdout != "" {
+                               s.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
+                       }
+                       if stderr != "" {
+                               s.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
+                       }
+               }
+
+               // Temporary "Hack": Buffered sent data to avoid freeze in web Browser
+               // FIXME: remove bufStdout & bufStderr and implement better algorithm
+               s.bufStdout += stdout
+               s.bufStderr += stderr
+               if len(s.bufStdout) > SizeBufStdout || len(s.bufStderr) > SizeBufStderr {
+                       // Emit event
+                       err := (*so).Emit(xsapiv1.EVTSDKInstall, xsapiv1.SDKManagementMsg{
+                               CmdID:     e.CmdID,
+                               Timestamp: time.Now().String(),
+                               Sdk:       s.sdk,
+                               Progress:  0, // TODO add progress
+                               Exited:    false,
+                               Stdout:    s.bufStdout,
+                               Stderr:    s.bufStderr,
+                       })
+                       if err != nil {
+                               s.Log.Errorf("WS Emit : %v", err)
+                       }
+                       s.bufStdout = ""
+                       s.bufStderr = ""
+               }
+       }
+
+       // Define callback for output
+       s.installCmd.ExitCB = func(e *eows.ExecOverWS, code int, exitError error) {
+               // paranoia
+               data := e.UserData
+               sdkID := (*data)["SDKID"].(string)
+               if sdkID != s.sdk.ID {
+                       s.Log.Errorln("BUG: sdk ID differs: %v != %v", sdkID, s.sdk.ID)
+               }
+
+               s.Log.Debugf("Command SDK ID %s [Cmd ID %s]  exited: code %d, exitError: %v", sdkID[:16], e.CmdID, code, exitError)
+
+               // IO socket can be nil when disconnected
+               so := s.sessions.IOSocketGet(e.Sid)
+               if so == nil {
+                       s.Log.Infof("%s (exit) not emitted - WS closed (id:%s)", xsapiv1.EVTSDKInstall, e.CmdID)
+                       return
+               }
+
+               // Emit event remaining data in bufStdout/err
+               if len(s.bufStderr) > 0 || len(s.bufStdout) > 0 {
+                       err := (*so).Emit(xsapiv1.EVTSDKInstall, xsapiv1.SDKManagementMsg{
+                               CmdID:     e.CmdID,
+                               Timestamp: time.Now().String(),
+                               Sdk:       s.sdk,
+                               Progress:  50, // TODO add progress
+                               Exited:    false,
+                               Stdout:    s.bufStdout,
+                               Stderr:    s.bufStderr,
+                       })
+                       if err != nil {
+                               s.Log.Errorf("WS Emit : %v", err)
+                       }
+                       s.bufStdout = ""
+                       s.bufStderr = ""
+               }
+
+               // Update SDK status
+               if code == 0 && exitError == nil {
+                       s.sdk.LastError = ""
+                       s.sdk.Status = xsapiv1.SdkStatusInstalled
+               } else {
+                       s.sdk.LastError = "Installation failed (code " + strconv.Itoa(code) +
+                               ")"
+                       if exitError != nil {
+                               s.sdk.LastError = ". Error: " + exitError.Error()
+                       }
+                       s.sdk.Status = xsapiv1.SdkStatusNotInstalled
+               }
+
+               emitErr := ""
+               if exitError != nil {
+                       emitErr = exitError.Error()
+               }
+               if emitErr == "" && s.sdk.LastError != "" {
+                       emitErr = s.sdk.LastError
+               }
+
+               // Emit event
+               errSoEmit := (*so).Emit(xsapiv1.EVTSDKInstall, xsapiv1.SDKManagementMsg{
+                       CmdID:     e.CmdID,
+                       Timestamp: time.Now().String(),
+                       Sdk:       s.sdk,
+                       Progress:  100,
+                       Exited:    true,
+                       Code:      code,
+                       Error:     emitErr,
+               })
+               if errSoEmit != nil {
+                       s.Log.Errorf("WS Emit : %v", errSoEmit)
+               }
+
+               // Cleanup command for the next time
+               s.installCmd = nil
+       }
+
+       // User data (used within callbacks)
+       data := make(map[string]interface{})
+       data["SDKID"] = s.sdk.ID
+       s.installCmd.UserData = &data
+
+       // Start command execution
+       s.Log.Infof("Install SDK %s: cmdID=%v, cmd=%v, args=%v", s.sdk.Name, s.installCmd.CmdID, s.installCmd.Cmd, s.installCmd.Args)
+
+       s.sdk.Status = xsapiv1.SdkStatusInstalling
+       s.sdk.LastError = ""
+
+       err := s.installCmd.Start()
+
+       return err
+}
+
+// AbortInstallRemove abort an install or remove command
+func (s *CrossSDK) AbortInstallRemove(timeout int) error {
+
+       if s.installCmd == nil {
+               return fmt.Errorf("no installation in progress for this sdk")
+       }
+
+       s.sdk.Status = xsapiv1.SdkStatusNotInstalled
+       return s.installCmd.Signal("SIGKILL")
+}
+
+// Remove Used to remove/uninstall a SDK
+func (s *CrossSDK) Remove() error {
+
+       if s.sdk.Status != xsapiv1.SdkStatusInstalled {
+               return fmt.Errorf("this sdk is not installed")
+       }
+
+       s.sdk.Status = xsapiv1.SdkStatusUninstalling
+
+       cmdline := s.scripts[scriptRemove] + " " + s.sdk.Path
+       cmd := exec.Command(cmdline)
+       stdout, err := cmd.CombinedOutput()
+       if err != nil {
+               return fmt.Errorf("Error while uninstalling sdk: %v", err)
+       }
+       s.Log.Debugf("SDK uninstall output:\n %v", stdout)
+
+       return nil
+}
+
 // Get Return SDK definition
 func (s *CrossSDK) Get() *xsapiv1.SDK {
        return &s.sdk
@@ -69,5 +397,5 @@ func (s *CrossSDK) Get() *xsapiv1.SDK {
 
 // GetEnvCmd returns the command used to initialized the environment
 func (s *CrossSDK) GetEnvCmd() []string {
-       return []string{"source", s.sdk.EnvFile}
+       return []string{"source", s.sdk.SetupFile}
 }
index 35aa0ba..38e380d 100644 (file)
@@ -26,6 +26,7 @@ import (
 
        common "github.com/iotbzh/xds-common/golib"
        "github.com/iotbzh/xds-server/lib/xsapiv1"
+       uuid "github.com/satori/go.uuid"
 )
 
 // SDKs List of installed SDK
@@ -34,6 +35,7 @@ type SDKs struct {
        Sdks map[string]*CrossSDK
 
        mutex sync.Mutex
+       stop  chan struct{} // signals intentional stop
 }
 
 // NewSDKs creates a new instance of SDKs
@@ -41,40 +43,140 @@ func NewSDKs(ctx *Context) (*SDKs, error) {
        s := SDKs{
                Context: ctx,
                Sdks:    make(map[string]*CrossSDK),
+               stop:    make(chan struct{}),
        }
 
-       // Retrieve installed sdks
-       sdkRD := ctx.Config.FileConf.SdkRootDir
+       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
+       }
 
-       if common.Exists(sdkRD) {
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
 
-               // Assume that SDK install tree is <rootdir>/<profile>/<version>/<arch>
-               dirs, err := filepath.Glob(path.Join(sdkRD, "*", "*", "*"))
+       // Foreach directories in scripts/sdk
+       nbInstalled := 0
+       monSdksPath := make(map[string]*xsapiv1.SDKFamilyConfig)
+       for _, d := range dirs {
+               if !common.IsDir(d) {
+                       continue
+               }
+
+               sdksList, err := ListCrossSDK(d, s.Log)
                if err != nil {
-                       ctx.Log.Debugf("Error while retrieving SDKs: dir=%s, error=%s", sdkRD, err.Error())
                        return &s, err
                }
-               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 := NewCrossSDK(ctx, sdk, d)
                        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
                        }
+                       if _, exist := s.Sdks[cSdk.sdk.ID]; exist {
+                               s.Log.Warningf("Duplicate SDK ID : %v", cSdk.sdk.ID)
+                               cSdk.sdk.ID += "_DUPLICATE_" + uuid.NewV1().String()
+                       }
                        s.Sdks[cSdk.sdk.ID] = cSdk
+                       if cSdk.sdk.Status == xsapiv1.SdkStatusInstalled {
+                               nbInstalled++
+                       }
+
+                       monSdksPath[cSdk.sdk.FamilyConf.RootDir] = &cSdk.sdk.FamilyConf
                }
        }
 
-       ctx.Log.Debugf("SDKs: %d cross sdks found", len(s.Sdks))
+       ctx.Log.Debugf("Cross SDKs: %d defined, %d installed", len(s.Sdks), nbInstalled)
+
+       // Start monitor thread to detect new SDKs
+       if len(monSdksPath) == 0 {
+               s.Log.Warningf("No cross SDKs definition found")
+       }
 
        return &s, nil
 }
 
+// Stop SDKs management
+func (s *SDKs) Stop() {
+       close(s.stop)
+}
+
+// monitorSDKInstallation
+/* TODO: cleanup
+func (s *SDKs) monitorSDKInstallation(monSDKs map[string]*xsapiv1.SDKFamilyConfig) {
+
+       // 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 monSDKs {
+               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:
+                               // Emit Folder state change event
+                               if err := s.events.Emit(xsapiv1.EVTSDKInstall, 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 == "" {
@@ -108,6 +210,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()
@@ -142,3 +257,80 @@ 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, sess *ClientSession) (*xsapiv1.SDK, error) {
+       var cSdk *CrossSDK
+       if id != "" && filepath != "" {
+               return nil, fmt.Errorf("invalid parameter, both id and filepath are set")
+       }
+       if id != "" {
+               var exist bool
+               cSdk, exist = s.Sdks[id]
+               if !exist {
+                       return nil, fmt.Errorf("unknown id")
+               }
+       } else if filepath != "" {
+               // TODO check that file is accessible
+
+       } else {
+               return nil, fmt.Errorf("invalid parameter, id or filepath must be set")
+       }
+
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
+
+       // Launch script to install
+       // (note that add event will be generated by monitoring thread)
+       if err := cSdk.Install(filepath, force, timeout, 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) (*xsapiv1.SDK, error) {
+       s.mutex.Lock()
+       defer s.mutex.Unlock()
+
+       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(); 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
+}
index 3b5b239..27b212b 100644 (file)
@@ -127,6 +127,7 @@ func (s *WebServer) Serve() error {
        case <-s.stop:
                // Shutting down permanently
                s.sessions.Stop()
+               s.sdks.Stop()
                s.Log.Infoln("shutting down (stop)")
        case err = <-serveError:
                // Error due to listen/serve failure
index 1552579..84e62c1 100644 (file)
@@ -24,8 +24,8 @@ import (
 
 // EventRegisterArgs Parameters (json format) of /events/register command
 type EventRegisterArgs struct {
-       Name      string `json:"name"`
-       ProjectID string `json:"filterProjectID"`
+       Name   string `json:"name"`
+       Filter string `json:"filter"`
 }
 
 // EventUnRegisterArgs Parameters of /events/unregister command
@@ -49,14 +49,18 @@ const (
 
        // Supported Events type
        EVTAll               = EventTypePrefix + "all"
-       EVTFolderChange      = EventTypePrefix + "folder-change"       // type EventMsg with Data type xsapiv1.???
-       EVTFolderStateChange = EventTypePrefix + "folder-state-change" // type EventMsg with Data type xsapiv1.???
+       EVTFolderChange      = EventTypePrefix + "folder-change"       // type EventMsg with Data type xsapiv1.FolderConfig
+       EVTFolderStateChange = EventTypePrefix + "folder-state-change" // type EventMsg with Data type xsapiv1.FolderConfig
+       EVTSDKInstall        = EventTypePrefix + "sdk-install"         // type EventMsg with Data type xsapiv1.SDKManagementMsg
+       EVTSDKRemove         = EventTypePrefix + "sdk-remove"          // type EventMsg with Data type xsapiv1.SDKManagementMsg
 )
 
 // EVTAllList List of all supported events
 var EVTAllList = []string{
        EVTFolderChange,
        EVTFolderStateChange,
+       EVTSDKInstall,
+       EVTSDKRemove,
 }
 
 // DecodeFolderConfig Helper to decode Data field type FolderConfig
index 4bc390a..3a79c99 100644 (file)
 
 package xsapiv1
 
+// SDK status definition
+const (
+       SdkStatusDisable      = "Disable"
+       SdkStatusNotInstalled = "Not Installed"
+       SdkStatusInstalling   = "Installing"
+       SdkStatusUninstalling = "Un-installing"
+       SdkStatusInstalled    = "Installed"
+)
+
 // SDK Define a cross tool chain used to build application
 type SDK struct {
-       ID      string `json:"id" binding:"required"`
-       Name    string `json:"name"`
-       Profile string `json:"profile"`
-       Version string `json:"version"`
-       Arch    string `json:"arch"`
-       Path    string `json:"path"`
+       ID          string `json:"id" binding:"required"`
+       Name        string `json:"name"`
+       Description string `json:"description"`
+       Profile     string `json:"profile"`
+       Version     string `json:"version"`
+       Arch        string `json:"arch"`
+       Path        string `json:"path"`
+       URL         string `json:"url"`
+       Status      string `json:"status"`
+       Date        string `json:"date"`
+       Size        string `json:"size"`
+       Md5sum      string `json:"md5sum"`
+       SetupFile   string `json:"setupFile"`
+       LastError   string `json:"lastError"`
 
        // Not exported fields
-       EnvFile string `json:"-"`
+       FamilyConf SDKFamilyConfig `json:"-"`
+}
+
+// SDKFamilyConfig Configuration structure to define a SDKs family
+type SDKFamilyConfig struct {
+       FamilyName   string `json:"familyName"`
+       Description  string `json:"description"`
+       RootDir      string `json:"rootDir"`
+       EnvSetupFile string `json:"envSetupFilename"`
+       ScriptsDir   string `json:"scriptsDir"`
+}
+
+// SDKInstallArgs JSON parameters of POST /sdks or /sdks/abortinstall commands
+type SDKInstallArgs struct {
+       ID       string `json:"id" binding:"required"` // install by ID (must be part of GET /sdks result)
+       Filename string `json:"filename"`              // install by using a file
+       Force    bool   `json:"force"`                 // force SDK install when already existing
+       Timeout  int    `json:"timeout"`               // 1800 == default 30 minutes
+}
+
+// SDKManagementMsg Message send during SDK installation or when installation is complete
+type SDKManagementMsg struct {
+       CmdID     string `json:"cmdID"`
+       Timestamp string `json:"timestamp"`
+       Sdk       SDK    `json:"sdk"`
+       Stdout    string `json:"stdout"`
+       Stderr    string `json:"stderr"`
+       Progress  int    `json:"progress"` // 0 = not started to 100% = complete
+       Exited    bool   `json:"exited"`
+       Code      int    `json:"code"`
+       Error     string `json:"error"`
 }
diff --git a/scripts/sdks/README.md b/scripts/sdks/README.md
new file mode 100644 (file)
index 0000000..8b741ec
--- /dev/null
@@ -0,0 +1,78 @@
+# SDKs management scripts
+
+To support a new SDK family, you must create a new directory under
+`scripts/sdk/xxx` where xxx is the new SDK family.
+
+Then you must create the following scripts (or executable) :
+
+- `get-config`: returned SDK configuration structure
+- `list`: returned the list of installed SDKs
+- `add`: add a new SDK
+- `remove`: remove an existing SDK
+
+## `get-config`
+
+Returned SDK configuration as json format:
+
+```json
+{
+    "familyName": "xxx",
+    "description": "bla bla",
+    "rootDir": "/yyy/zzz",
+    "envSetupFilename": "my-envfilename*",
+    "scriptsDir": "scripts_path"
+}
+```
+
+where:
+
+- `familyName` : sdk familyName (usually same name used as xxx directory)
+- `rootDir` : root directory where SDK are/will be  installed
+- `envSetupFilename` : sdk files (present in each sdk) that will be sourced to
+  setup sdk environment
+
+## `list`
+
+Returned the list all SDKs (available and installed)
+
+```json
+[
+  {
+    "name":         "My SDK name",
+    "description":  "A description",
+    "profile":      "profile",
+    "version":      "version",
+    "arch":         "architecture",
+    "path":         "path where sdk installed locally",
+    "url":          "https://website.url.to.download.sdk",
+    "status":       "Not Installed | Installed",
+    "date":         "2017-12-25 00:00",
+    "size":         "123 MB",
+    "md5sum":       "123456789",
+    "setupFile":    "path to file to setup SDK environment"
+  }, {
+    "name":         "My SDK name 2",
+    "description":  "A description 2",
+    ...
+  }
+  ...
+]
+```
+
+## `add`
+
+add a new SDK
+
+List of parameters to implement:
+
+- `-f|--file <filepath>` :  install a SDK using a file
+- `--force`:                force SDK install when a SDK already in the same destination directory
+- `-u|--url <url>` :        download SDK using this URL and then install it
+- `-no-clean` :             don't cleanup temporary files
+- `-h|--help` :             display help
+
+## `remove`
+
+Remove an existing SDK
+
+The first argument is the full path of the directory of the SDK to removed.
diff --git a/scripts/sdks/agl/_build-sdks-json.sh b/scripts/sdks/agl/_build-sdks-json.sh
new file mode 100755 (executable)
index 0000000..82cb2f3
--- /dev/null
@@ -0,0 +1,121 @@
+#!/bin/bash
+###########################################################################
+# Copyright 2017 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.
+###########################################################################
+
+SDK_AGL_BASEURL="https://download.automotivelinux.org/AGL"
+SDK_AGL_IOTBZH_BASEURL="http://iot.bzh/download/public/XDS/sdk"
+
+# Define urls where SDKs can be downloaded
+DOWNLOADABLE_URLS="
+    ${SDK_AGL_BASEURL}/snapshots/master/latest/*/deploy/sdk
+
+    ${SDK_AGL_BASEURL}/release/dab/3.99.3/m3ulcb-nogfx/deploy/sdk
+    ${SDK_AGL_BASEURL}/release/dab/4.0.2/*/deploy/sdk
+
+    ${SDK_AGL_BASEURL}/release/eel/4.99.4/*/deploy/sdk
+    ${SDK_AGL_BASEURL}/release/eel/latest/*/deploy/sdk
+
+    ${SDK_AGL_IOTBZH_BASEURL}
+"
+
+###
+
+
+# Compute full urls list  (parse '*' characters)
+urls=""
+for url in $(echo $DOWNLOADABLE_URLS); do
+    if [[ "$url" = *"*"* ]]; then
+        bUrl=$(echo $url | cut -d'*' -f 1)
+        eUrl=$(echo $url | cut -d'*' -f 2)
+        dirs=$(curl -s ${bUrl} | grep '\[DIR\]' | grep -oP  'href="[^"]*"' | cut -d'"' -f 2)
+        for dir in $(echo $dirs); do
+            urls="$urls ${bUrl::-1}/${dir::-1}/${eUrl:1}"
+        done
+    else
+        urls="$urls $url"
+    fi
+done
+
+# Compute list of available/installable SDKs
+sdksList=" "
+for url in $(echo $urls); do
+    htmlPage=$(curl -s --connect-timeout 10 "${url}/")
+    files=$(echo ${htmlPage} | egrep -o 'href="[^"]*.sh"' | cut -d '"' -f 2)
+    if [ "$?" != "0" ] || [ "${files}" = "" ]; then
+        echo " IGNORED ${url}: no valid files found"
+        continue
+    fi
+
+    for sdkFile in $(echo ${files}); do
+
+        # assume that sdk name follow this format :
+        #  _PROFILE_-_COMPILER_ARCH_-_TARGET_-crosssdk-_ARCH_-toolchain-_VERSION_.sh
+        # for example:
+        #  poky-agl-glibc-x86_64-agl-demo-platform-crosssdk-corei7-64-toolchain-4.0.1.sh
+
+        [[ "${sdkFile}" != *"crosssdk"* ]] && { echo " IGNORED ${sdkFile}, not a valid sdk file"; continue; }
+
+        echo "Processing ${sdkFile}"
+        profile=$(echo "${sdkFile}" | sed -r 's/(.*)-glibc.*/\1/')
+        version=$(echo "${sdkFile}" | sed -r 's/.*toolchain-(.*).sh/\1/')
+        arch=$(echo "${sdkFile}" | sed -r 's/.*crosssdk-(.*)-toolchain.*/\1/')
+
+        endUrl=${url#$SDK_AGL_BASEURL}
+        if [ "${endUrl::4}" = "http" ]; then
+            name=${profile}_${arch}_${version}
+        else
+            name=$(echo "AGL-$(echo ${endUrl} | cut -d'/' -f2,3,4,5)" | sed s:/:-:g)
+        fi
+
+        [ "${profile}" = "" ] && { echo " ERROR: profile not set" continue; }
+        [ "${version}" = "" ] && { echo " ERROR: version not set" continue; }
+        [ "${arch}" = "" ] && { echo " ERROR: arch not set" continue; }
+        [ "${name}" = "" ] && { name=${profile}_${arch}_${version}; }
+
+        sdkDate="$(echo "${htmlPage}" |egrep -o ${sdkFile/+/\\+}'</a>.*[0-9\-]+ [0-9]+:[0-9]+' |cut -d'>' -f 4|cut -d' ' -f1,2)"
+        sdkSize="$(echo "${htmlPage}" |egrep -o  "${sdkFile/+/\\+}.*${sdkDate}.*[0-9\.MG]+</td>" |cut -d'>' -f7 |cut -d'<' -f1)"
+        md5sum="$(wget -q -O - ${url}/${sdkFile/.sh/.md5} |cut -d' ' -f1)"
+
+        read -r -d '' res <<- EndOfMessage
+{
+    "name":         "${name}",
+    "description":  "AGL SDK ${arch} (version ${version})",
+    "profile":      "${profile}",
+    "version":      "${version}",
+    "arch":         "${arch}",
+    "path":         "",
+    "url":          "${url}/${sdkFile}",
+    "status":       "Not Installed",
+    "date":         "${sdkDate}",
+    "size":         "${sdkSize}",
+    "md5sum":       "${md5sum}",
+    "setupFile":    ""
+},
+EndOfMessage
+
+        sdksList="${sdksList}${res}"
+    done
+done
+
+OUT_FILE=$(dirname "$0")/sdks_$(date +"%F_%H%m").json
+
+echo "[" > ${OUT_FILE}
+echo "${sdksList::-1}" >> ${OUT_FILE}
+echo "]" >> ${OUT_FILE}
+
+echo "SDKs list successfully saved in ${OUT_FILE}"
diff --git a/scripts/sdks/agl/_env-init.sh b/scripts/sdks/agl/_env-init.sh
new file mode 100755 (executable)
index 0000000..0f423c4
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/bash
+ ###########################################################################
+# Copyright 2017 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.
+###########################################################################
+
+. /etc/xdtrc
+
+[ -z "$XDT_SDK" ] && XDT_SDK=/xdt/sdk
+
+export SDK_FAMILY_NAME="agl"
+export SDK_ROOT_DIR="$XDT_SDK"
+export SDK_ENV_SETUP_FILENAME="environment-setup-*"
+export SDK_DATABASE="http://iot.bzh/download/public/XDS/sdk/sdks_latest.json"
+
+[ "$1" = "-print" ] && { env; }
diff --git a/scripts/sdks/agl/add b/scripts/sdks/agl/add
new file mode 100755 (executable)
index 0000000..dcb3833
--- /dev/null
@@ -0,0 +1,103 @@
+#!/bin/bash
+ ###########################################################################
+# Copyright 2017 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.
+###########################################################################
+
+. $(dirname "$0")/_env-init.sh
+
+usage() {
+    echo "Usage: $(basename $0) [-h|--help] [-f|--file <sdk-filename>] [-u|--url <https_url>] [--force] [--no-clean]"
+       exit 1
+}
+
+TMPDIR=""
+SDK_FILE=""
+URL=""
+do_cleanup=true
+do_force=false
+while [ $# -ne 0 ]; do
+    case $1 in
+        -f|--file)
+            shift
+            SDK_FILE=$1
+            ;;
+        --force)
+            do_force=true
+            ;;
+        -u|--url)
+            shift
+            URL=$1
+            ;;
+        -no-clean)
+            do_cleanup=false
+            ;;
+        -h|--help)
+            usage
+            ;;
+        *)
+            echo "Invalid argument: $1"
+            usage
+            ;;
+    esac
+    shift
+done
+
+[ "$SDK_FILE" = "" ] && [ "$URL" = "" ] && { echo "--file or --url option must be set"; exit 1; }
+
+# Create SDK root dir if needed
+[ ! -d ${SDK_ROOT_DIR} ] && mkdir -p ${SDK_ROOT_DIR}
+cd ${SDK_ROOT_DIR} || exit 1
+
+# Cleanup
+trap "cleanExit" 0 1 2 15
+cleanExit ()
+{
+    if ($do_cleanup); then
+        [[ -d ${TMPDIR} ]] && rm -rf ${TMPDIR}
+    fi
+}
+
+# Download sdk
+if [ "$URL" != "" ]; then
+    TMPDIR=$(mktemp -d)
+    SDK_FILE=${TMPDIR}/$(basename ${URL})
+    echo "Downloading $(basename ${SDK_FILE}) ..."
+    wget "$URL" -O "${SDK_FILE}" || exit 1
+fi
+
+# Retreive default install dir to extract version
+offset=$(grep -na -m1 "^MARKER:$" "${SDK_FILE}" | cut -d':' -f1)
+eval $(head -n $offset "${SDK_FILE}" | grep ^DEFAULT_INSTALL_DIR= )
+
+PROFILE=$(basename $(dirname $DEFAULT_INSTALL_DIR))
+VERSION=$(basename $DEFAULT_INSTALL_DIR)
+ARCH=$(echo "$SDK_FILE" | sed -r 's/.*crosssdk-(.*)-toolchain.*/\1/')
+
+[ "$PROFILE" = "" ] && { echo "PROFILE is not set"; exit 1; }
+[ "$VERSION" = "" ] && { echo "VERSION is not set"; exit 1; }
+[ "$ARCH" = "" ] && { echo "ARCH is not set"; exit 1; }
+
+DESTDIR=${SDK_ROOT_DIR}/${PROFILE}/${VERSION}/${ARCH}
+
+[ -d ${DESTDIR} ] && [ "$do_force" != "true" ] && { echo "SDK already installed in $DESTDIR"; exit 1; }
+
+# Cleanup previous install
+rm -rf ${DESTDIR} && mkdir -p ${DESTDIR} || exit 1
+
+# Install sdk
+chmod +x ${SDK_FILE}
+${SDK_FILE} -y -d ${DESTDIR}
diff --git a/scripts/sdks/agl/get-config b/scripts/sdks/agl/get-config
new file mode 100755 (executable)
index 0000000..8d370b8
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/bash
+ ###########################################################################
+# Copyright 2017 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.
+###########################################################################
+
+SCRIPTS_DIR=$(dirname "$0")
+. ${SCRIPTS_DIR}/_env-init.sh
+
+read -r -d '' res <<- EndOfMessage
+{
+    "familyName":       "${SDK_FAMILY_NAME}",
+    "description":      "Automotive Grade Linux SDK",
+    "rootDir":          "${SDK_ROOT_DIR}",
+    "envSetupFilename": "${SDK_ENV_SETUP_FILENAME}",
+    "scriptsDir":       "${SCRIPTS_DIR}"
+}
+EndOfMessage
+
+echo "$res"
diff --git a/scripts/sdks/agl/list b/scripts/sdks/agl/list
new file mode 100755 (executable)
index 0000000..dc748a4
--- /dev/null
@@ -0,0 +1,137 @@
+#! /usr/bin/env node
+
+/**************************************************************************
+ * Copyright 2017 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.
+ **************************************************************************/
+
+const fs = require('fs');
+const process = require('process');
+const execSync = require('child_process').execSync;
+const path = require('path');
+
+
+// Only used for debug purpose
+const DEBUG = false || (process.argv.length > 2 && process.argv[2] == '-debug');
+dbgPrint = function () {
+    if (DEBUG) console.log.apply(console, arguments);
+}
+// Get env vars
+var envMap = {};
+envData = execSync(path.join(__dirname, '_env-init.sh -print'));
+envData.toString().split('\n').forEach(e => envMap[e.split('=')[0]] = e.split('=')[1]);
+const opts = {
+    cwd: __dirname,
+    env: envMap
+};
+
+// Get list of available SDKs
+sdksDBFile = path.join(__dirname, "sdks_latest.json")
+try {
+    // Fetch SDK Json database file when not existing
+    if (!fs.existsSync(sdksDBFile)) {
+        var data = execSync(path.join(__dirname, 'update'), opts);
+    }
+    // Read SDK Json database file
+    var data = fs.readFileSync(sdksDBFile);
+    var sdks = JSON.parse(data);
+
+    // Force some default fields value
+    sdks.forEach(sdk => {
+        sdk.status = 'Not Installed';
+    });
+} catch (err) {
+    dbgPrint('ERROR: ', err);
+    process.exit(-1)
+}
+
+// Get list of installed SDKs
+try {
+    const cmd = 'find "${SDK_ROOT_DIR}" -maxdepth 4 -name "${SDK_ENV_SETUP_FILENAME}"';
+    var data = execSync(cmd, opts);
+    data.toString().split('\n').forEach(envFile => {
+        if (envFile == '') return;
+
+        dbgPrint('Processing ', envFile);
+        const profile = envFile.split('/')[3];
+        const version = envFile.split('/')[4];
+        const arch = envFile.split('/')[5];
+        const dir = path.dirname(envFile);
+        if (profile == '' || version == '' || arch == '' || dir == '') {
+            return;
+        }
+
+        sdkDate = ''
+        versionFile = path.join(path.dirname(envFile), 'version-*')
+        try {
+            cmdVer = "[ -f " + versionFile + " ] && grep Timestamp " + versionFile + " |cut -d' ' -f2"
+            var data = execSync(cmdVer);
+        } catch (err) {
+            dbgPrint('IGNORING SDK ', dir);
+            dbgPrint(err.toString());
+            if (DEBUG) {
+                process.exit(-1);
+            } else {
+                return;
+            }
+        }
+        d = data.toString()
+        if (d != "") {
+            sdkDate = d.substring(0, 4) + "-" + d.substring(4, 6) + "-" + d.substring(6, 8)
+            sdkDate += " " + d.substring(8, 10) + ":" + d.substring(10, 12)
+        }
+
+        var found = false;
+        sdks.forEach(sdk => {
+            // Update sdk with local info when found
+            if (profile == sdk.profile && version == sdk.version && arch == sdk.arch) {
+                found = true;
+                dbgPrint(" OK found, updating...");
+                sdk.path = dir;
+                sdk.status = 'Installed';
+                sdk.data = sdkDate;
+                sdk.setupFile = envFile;
+                return
+            }
+        });
+        if (found == false) {
+            dbgPrint(" NOT found in database, adding it...");
+            sdks.push({
+                name: profile + '-' + arch + '-' + version,
+                description: 'AGL SDK ' + arch + ' (version ' + version + ')',
+                profile: profile,
+                version: version,
+                arch: arch,
+                path: dir,
+                url: "",
+                status: "Installed",
+                date: sdkDate,
+                size: "",
+                md5sum: "",
+                setupFile: envFile
+            });
+        }
+    });
+
+} catch (err) {
+    dbgPrint('ERROR: ', err);
+    process.exit(-1)
+}
+
+// Print result
+console.log(JSON.stringify(sdks));
+
+process.exit(0)
diff --git a/scripts/sdks/agl/remove b/scripts/sdks/agl/remove
new file mode 100755 (executable)
index 0000000..99a4022
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/bash
+ ###########################################################################
+# Copyright 2017 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.
+###########################################################################
+
+. $(dirname "$0")/_env-init.sh
+
+if [[ "${1}" == "" || "${1}" != "${SDK_ROOT_DIR}"* ]]; then
+    echo "Invalid sdk root directory"
+    exit 1
+fi
+
+if [ ! -d "${1}" ]; then
+    echo "sdk directory doesn't exist"
+    exit 1
+fi
+
+rm -rf "${1}"
diff --git a/scripts/sdks/agl/update b/scripts/sdks/agl/update
new file mode 100755 (executable)
index 0000000..e59c8fa
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/bash
+ ###########################################################################
+# Copyright 2017 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.
+###########################################################################
+
+. $(dirname "$0")/_env-init.sh
+
+wget -q --connect-timeout=30 ${SDK_DATABASE}
index 4d9eadf..0491b24 100755 (executable)
@@ -19,7 +19,7 @@
 
 . /etc/xdtrc
 
-[ -z "$SDK_BASEURL" ] && SDK_BASEURL="http://iot.bzh/download/public/2017/XDS/sdk/"
+[ -z "$SDK_BASEURL" ] && SDK_BASEURL="http://iot.bzh/download/public/XDS/sdk/"
 [ -z "$XDT_SDK" ] && XDT_SDK=/xdt/sdk
 
 # Support only poky_agl profile for now
index 3109d2e..68b622f 100644 (file)
@@ -92,7 +92,7 @@
             </a>
         </li>
         <li>
-            <a href="http://iot.bzh/download/public/2017/XDS/docs/XDS_UsersGuide.pdf">
+            <a href="http://iot.bzh/download/public/XDS/docs/XDS_UsersGuide.pdf">
                 User's guide (PDF file)
                 <i class="fa fa-external-link" aria-hidden="true"></i>
             </a>