summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2017-12-22 21:26:40 +0100
committerSebastien Douheret <sebastien.douheret@iot.bzh>2017-12-22 21:29:59 +0100
commitf1c182ede3c4aed0d6196d05b0a64ff93372e755 (patch)
tree2cf95732a06808aac8325bccb5199346b33165a2 /lib
parent285332c351777b74abca638b8b2a2cde3c68edc6 (diff)
Added SDKs management support.
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
Diffstat (limited to 'lib')
-rw-r--r--lib/syncthing/st.go2
-rw-r--r--lib/xdsconfig/config.go4
-rw-r--r--lib/xdsconfig/fileconfig.go18
-rw-r--r--lib/xdsserver/apiv1-events.go2
-rw-r--r--lib/xdsserver/apiv1-sdks.go79
-rw-r--r--lib/xdsserver/apiv1.go3
-rw-r--r--lib/xdsserver/sdk.go370
-rw-r--r--lib/xdsserver/sdks.go222
-rw-r--r--lib/xdsserver/webserver.go1
-rw-r--r--lib/xsapiv1/events.go12
-rw-r--r--lib/xsapiv1/sdks.go61
11 files changed, 715 insertions, 59 deletions
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 .../<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}
}
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 <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
+}
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"`
}