From f1c182ede3c4aed0d6196d05b0a64ff93372e755 Mon Sep 17 00:00:00 2001 From: Sebastien Douheret Date: Fri, 22 Dec 2017 21:26:40 +0100 Subject: Added SDKs management support. Signed-off-by: Sebastien Douheret --- .gitignore | 1 + .vscode/launch.json | 6 + .vscode/settings.json | 3 +- Makefile | 2 +- conf.d/etc/xds/server/server-config.json | 2 +- lib/syncthing/st.go | 2 +- lib/xdsconfig/config.go | 4 +- lib/xdsconfig/fileconfig.go | 18 +- lib/xdsserver/apiv1-events.go | 2 + lib/xdsserver/apiv1-sdks.go | 79 +++++++ lib/xdsserver/apiv1.go | 3 + lib/xdsserver/sdk.go | 370 +++++++++++++++++++++++++++++-- lib/xdsserver/sdks.go | 222 +++++++++++++++++-- lib/xdsserver/webserver.go | 1 + lib/xsapiv1/events.go | 12 +- lib/xsapiv1/sdks.go | 61 ++++- scripts/sdks/README.md | 78 +++++++ scripts/sdks/agl/_build-sdks-json.sh | 121 ++++++++++ scripts/sdks/agl/_env-init.sh | 29 +++ scripts/sdks/agl/add | 103 +++++++++ scripts/sdks/agl/get-config | 33 +++ scripts/sdks/agl/list | 137 ++++++++++++ scripts/sdks/agl/remove | 32 +++ scripts/sdks/agl/update | 22 ++ scripts/xds-utils/install-agl-sdks.sh | 2 +- webapp/src/index.html | 2 +- 26 files changed, 1283 insertions(+), 64 deletions(-) create mode 100644 scripts/sdks/README.md create mode 100755 scripts/sdks/agl/_build-sdks-json.sh create mode 100755 scripts/sdks/agl/_env-init.sh create mode 100755 scripts/sdks/agl/add create mode 100755 scripts/sdks/agl/get-config create mode 100755 scripts/sdks/agl/list create mode 100755 scripts/sdks/agl/remove create mode 100755 scripts/sdks/agl/update diff --git a/.gitignore b/.gitignore index a5007eb..debbc5d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ package debug cmd/*/debug *.zip +scripts/sdks/**/sdks*.json webapp/dist webapp/node_modules diff --git a/.vscode/launch.json b/.vscode/launch.json index 245c2ee..f976fb6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,6 +32,12 @@ }, "args": ["-log", "debug", "-c", "__config_local_dev.json"], "showLog": false + }, + { + "type": "node", + "request": "launch", + "name": "Script sdk list", + "program": "${workspaceFolder}/scripts/sdks/agl/list" } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 24aa012..6415d9e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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" ] } diff --git a/Makefile b/Makefile index 86107f1..adefb65 100644 --- 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: diff --git a/conf.d/etc/xds/server/server-config.json b/conf.d/etc/xds/server/server-config.json index 5562bb3..a27d4f5 100644 --- a/conf.d/etc/xds/server/server-config.json +++ b/conf.d/etc/xds/server/server-config.json @@ -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", diff --git a/lib/syncthing/st.go b/lib/syncthing/st.go index a52e1fe..bb50ea8 100644 --- a/lib/syncthing/st.go +++ b/lib/syncthing/st.go @@ -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) diff --git a/lib/xdsconfig/config.go b/lib/xdsconfig/config.go index 59cf394..74ce21e 100644 --- a/lib/xdsconfig/config.go +++ b/lib/xdsconfig/config.go @@ -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: "", diff --git a/lib/xdsconfig/fileconfig.go b/lib/xdsconfig/fileconfig.go index 8e77de7..bf8aa25 100644 --- a/lib/xdsconfig/fileconfig.go +++ b/lib/xdsconfig/fileconfig.go @@ -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 diff --git a/lib/xdsserver/apiv1-events.go b/lib/xdsserver/apiv1-events.go index 0942753..eedd747 100644 --- a/lib/xdsserver/apiv1-events.go +++ b/lib/xdsserver/apiv1-events.go @@ -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") diff --git a/lib/xdsserver/apiv1-sdks.go b/lib/xdsserver/apiv1-sdks.go index bcb293b..b38c418 100644 --- a/lib/xdsserver/apiv1-sdks.go +++ b/lib/xdsserver/apiv1-sdks.go @@ -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) +} diff --git a/lib/xdsserver/apiv1.go b/lib/xdsserver/apiv1.go index 143c25f..f9d5948 100644 --- a/lib/xdsserver/apiv1.go +++ b/lib/xdsserver/apiv1.go @@ -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) diff --git a/lib/xdsserver/sdk.go b/lib/xdsserver/sdk.go index 7a90f6b..c011d09 100644 --- a/lib/xdsserver/sdk.go +++ b/lib/xdsserver/sdk.go @@ -18,50 +18,378 @@ 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 .../// +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} } diff --git a/lib/xdsserver/sdks.go b/lib/xdsserver/sdks.go index 35aa0ba..38e380d 100644 --- a/lib/xdsserver/sdks.go +++ b/lib/xdsserver/sdks.go @@ -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 /// - 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 +} diff --git a/lib/xdsserver/webserver.go b/lib/xdsserver/webserver.go index 3b5b239..27b212b 100644 --- a/lib/xdsserver/webserver.go +++ b/lib/xdsserver/webserver.go @@ -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 diff --git a/lib/xsapiv1/events.go b/lib/xsapiv1/events.go index 1552579..84e62c1 100644 --- a/lib/xsapiv1/events.go +++ b/lib/xsapiv1/events.go @@ -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 diff --git a/lib/xsapiv1/sdks.go b/lib/xsapiv1/sdks.go index 4bc390a..3a79c99 100644 --- a/lib/xsapiv1/sdks.go +++ b/lib/xsapiv1/sdks.go @@ -17,15 +17,62 @@ 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 index 0000000..8b741ec --- /dev/null +++ b/scripts/sdks/README.md @@ -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 ` : install a SDK using a file +- `--force`: force SDK install when a SDK already in the same destination directory +- `-u|--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 index 0000000..82cb2f3 --- /dev/null +++ b/scripts/sdks/agl/_build-sdks-json.sh @@ -0,0 +1,121 @@ +#!/bin/bash +########################################################################### +# Copyright 2017 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. +########################################################################### + +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/+/\\+}'.*[0-9\-]+ [0-9]+:[0-9]+' |cut -d'>' -f 4|cut -d' ' -f1,2)" + sdkSize="$(echo "${htmlPage}" |egrep -o "${sdkFile/+/\\+}.*${sdkDate}.*[0-9\.MG]+" |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 index 0000000..0f423c4 --- /dev/null +++ b/scripts/sdks/agl/_env-init.sh @@ -0,0 +1,29 @@ +#!/bin/bash + ########################################################################### +# Copyright 2017 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. +########################################################################### + +. /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 index 0000000..dcb3833 --- /dev/null +++ b/scripts/sdks/agl/add @@ -0,0 +1,103 @@ +#!/bin/bash + ########################################################################### +# Copyright 2017 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. +########################################################################### + +. $(dirname "$0")/_env-init.sh + +usage() { + echo "Usage: $(basename $0) [-h|--help] [-f|--file ] [-u|--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 index 0000000..8d370b8 --- /dev/null +++ b/scripts/sdks/agl/get-config @@ -0,0 +1,33 @@ +#!/bin/bash + ########################################################################### +# Copyright 2017 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. +########################################################################### + +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 index 0000000..dc748a4 --- /dev/null +++ b/scripts/sdks/agl/list @@ -0,0 +1,137 @@ +#! /usr/bin/env node + +/************************************************************************** + * Copyright 2017 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. + **************************************************************************/ + +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 index 0000000..99a4022 --- /dev/null +++ b/scripts/sdks/agl/remove @@ -0,0 +1,32 @@ +#!/bin/bash + ########################################################################### +# Copyright 2017 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. +########################################################################### + +. $(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 index 0000000..e59c8fa --- /dev/null +++ b/scripts/sdks/agl/update @@ -0,0 +1,22 @@ +#!/bin/bash + ########################################################################### +# Copyright 2017 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. +########################################################################### + +. $(dirname "$0")/_env-init.sh + +wget -q --connect-timeout=30 ${SDK_DATABASE} diff --git a/scripts/xds-utils/install-agl-sdks.sh b/scripts/xds-utils/install-agl-sdks.sh index 4d9eadf..0491b24 100755 --- a/scripts/xds-utils/install-agl-sdks.sh +++ b/scripts/xds-utils/install-agl-sdks.sh @@ -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 diff --git a/webapp/src/index.html b/webapp/src/index.html index 3109d2e..68b622f 100644 --- a/webapp/src/index.html +++ b/webapp/src/index.html @@ -92,7 +92,7 @@
  • - + User's guide (PDF file) -- cgit 1.2.3-korg