2 * Copyright (C) 2017 "IoT.bzh"
3 * Author Sebastien Douheret <sebastien@iot.bzh>
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
30 "github.com/Sirupsen/logrus"
31 common "github.com/iotbzh/xds-common/golib"
32 "github.com/iotbzh/xds-common/golib/eows"
33 "github.com/iotbzh/xds-server/lib/xsapiv1"
34 uuid "github.com/satori/go.uuid"
37 // Definition of scripts used to managed SDKs
40 scriptGetConfig = "get-config"
42 scriptRemove = "remove"
43 scriptUpdate = "update"
46 var scriptsAll = []string{
56 // CrossSDK Hold SDK config
57 type CrossSDK struct {
60 scripts map[string]string
61 installCmd *eows.ExecOverWS
62 removeCmd *eows.ExecOverWS
68 // ListCrossSDK List all available and installed SDK (call "list" script)
69 func ListCrossSDK(scriptDir string, log *logrus.Logger) ([]xsapiv1.SDK, error) {
70 sdksList := []xsapiv1.SDK{}
72 // Retrieve SDKs list and info
73 cmd := exec.Command(path.Join(scriptDir, scriptList))
74 stdout, err := cmd.CombinedOutput()
76 return sdksList, fmt.Errorf("Cannot get sdks list: %v", err)
79 if err = json.Unmarshal(stdout, &sdksList); err != nil {
80 log.Errorf("SDK list script output:\n%v\n", string(stdout))
81 return sdksList, fmt.Errorf("Cannot decode sdk list %v", err)
87 // NewCrossSDK creates a new instance of Syncthing
88 func NewCrossSDK(ctx *Context, sdk xsapiv1.SDK, scriptDir string) (*CrossSDK, error) {
92 scripts: make(map[string]string),
95 // Execute get-config script to retrieve SDK configuration
96 getConfFile := path.Join(scriptDir, scriptGetConfig)
97 if !common.Exists(getConfFile) {
98 return &s, fmt.Errorf("'%s' script file not found in %s", scriptGetConfig, scriptDir)
101 cmd := exec.Command(getConfFile)
102 stdout, err := cmd.CombinedOutput()
104 return &s, fmt.Errorf("Cannot get sdk config using %s: %v", getConfFile, err)
107 err = json.Unmarshal(stdout, &s.sdk.FamilyConf)
109 s.Log.Errorf("SDK config script output:\n%v\n", string(stdout))
110 return &s, fmt.Errorf("Cannot decode sdk config %v", err)
112 famName := s.sdk.FamilyConf.FamilyName
115 if s.sdk.FamilyConf.RootDir == "" {
116 return &s, fmt.Errorf("SDK config not valid (rootDir not set)")
118 if s.sdk.FamilyConf.EnvSetupFile == "" {
119 return &s, fmt.Errorf("SDK config not valid (envSetupFile not set)")
122 // Check that other mandatory scripts are present
123 for _, scr := range scriptsAll {
124 s.scripts[scr] = path.Join(scriptDir, scr)
125 if !common.Exists(s.scripts[scr]) {
126 return &s, fmt.Errorf("Script named '%s' missing in SDK family '%s'", scr, famName)
130 // Fixed default fields value
132 if sdk.Status == "" {
133 sdk.Status = xsapiv1.SdkStatusNotInstalled
137 errMsg := "Invalid SDK definition "
139 return &s, fmt.Errorf(errMsg + "(name not set)")
140 } else if sdk.Profile == "" {
141 return &s, fmt.Errorf(errMsg + "(profile not set)")
142 } else if sdk.Version == "" {
143 return &s, fmt.Errorf(errMsg + "(version not set)")
144 } else if sdk.Arch == "" {
145 return &s, fmt.Errorf(errMsg + "(arch not set)")
147 if sdk.Status == xsapiv1.SdkStatusInstalled {
148 if sdk.SetupFile == "" {
149 return &s, fmt.Errorf(errMsg + "(setupFile not set)")
150 } else if !common.Exists(sdk.SetupFile) {
151 return &s, fmt.Errorf(errMsg + "(setupFile not accessible)")
154 return &s, fmt.Errorf(errMsg + "(path not set)")
155 } else if !common.Exists(sdk.Path) {
156 return &s, fmt.Errorf(errMsg + "(path not accessible)")
160 // Use V3 to ensure that we get same uuid on restart
163 nm = s.sdk.Profile + "_" + s.sdk.Arch + "_" + s.sdk.Version
165 s.sdk.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), nm).String()
167 s.LogSillyf("New SDK: ID=%v, Family=%s, Name=%v", s.sdk.ID[:8], s.sdk.FamilyConf.FamilyName, s.sdk.Name)
172 // Install a SDK (non blocking command, IOW run in background)
173 func (s *CrossSDK) Install(file string, force bool, timeout int, sess *ClientSession) error {
175 if s.sdk.Status == xsapiv1.SdkStatusInstalled {
176 return fmt.Errorf("already installed")
178 if s.sdk.Status == xsapiv1.SdkStatusInstalling {
179 return fmt.Errorf("installation in progress")
182 // Compute command args
183 cmdArgs := []string{}
185 cmdArgs = append(cmdArgs, "--file", file)
187 cmdArgs = append(cmdArgs, "--url", s.sdk.URL)
190 cmdArgs = append(cmdArgs, "--force")
195 cmdID := "sdk-install-" + strconv.Itoa(sdkCmdID)
197 // Create new instance to execute command and sent output over WS
198 s.installCmd = eows.New(s.scripts[scriptAdd], cmdArgs, sess.IOSocket, sess.ID, cmdID)
199 s.installCmd.Log = s.Log
201 s.installCmd.CmdExecTimeout = timeout
203 s.installCmd.CmdExecTimeout = 30 * 60 // default 30min
206 // FIXME: temporary hack
210 SizeBufStderr := 2000
211 if valS, ok := os.LookupEnv("XDS_SDK_BUF_STDOUT"); ok {
212 if valI, err := strconv.Atoi(valS); err == nil {
216 if valS, ok := os.LookupEnv("XDS_SDK_BUF_STDERR"); ok {
217 if valI, err := strconv.Atoi(valS); err == nil {
222 // Define callback for output (stdout+stderr)
223 s.installCmd.OutputCB = func(e *eows.ExecOverWS, stdout, stderr string) {
226 sdkID := (*data)["SDKID"].(string)
227 if sdkID != s.sdk.ID {
228 s.Log.Errorln("BUG: sdk ID differs: %v != %v", sdkID, s.sdk.ID)
231 // IO socket can be nil when disconnected
232 so := s.sessions.IOSocketGet(e.Sid)
234 s.Log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", xsapiv1.EVTSDKInstall, e.Sid, e.CmdID)
239 s.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - SDK ID:%s:", xsapiv1.EVTSDKInstall, e.Sid[4:], e.CmdID, sdkID[:16])
241 s.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
244 s.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
248 // Temporary "Hack": Buffered sent data to avoid freeze in web Browser
249 // FIXME: remove bufStdout & bufStderr and implement better algorithm
250 s.bufStdout += stdout
251 s.bufStderr += stderr
252 if len(s.bufStdout) > SizeBufStdout || len(s.bufStderr) > SizeBufStderr {
254 err := (*so).Emit(xsapiv1.EVTSDKInstall, xsapiv1.SDKManagementMsg{
256 Timestamp: time.Now().String(),
258 Progress: 0, // TODO add progress
264 s.Log.Errorf("WS Emit : %v", err)
271 // Define callback for output
272 s.installCmd.ExitCB = func(e *eows.ExecOverWS, code int, exitError error) {
275 sdkID := (*data)["SDKID"].(string)
276 if sdkID != s.sdk.ID {
277 s.Log.Errorln("BUG: sdk ID differs: %v != %v", sdkID, s.sdk.ID)
280 s.Log.Infof("Command SDK ID %s [Cmd ID %s] exited: code %d, exitError: %v", sdkID[:16], e.CmdID, code, exitError)
282 // IO socket can be nil when disconnected
283 so := s.sessions.IOSocketGet(e.Sid)
285 s.Log.Infof("%s (exit) not emitted - WS closed (id:%s)", xsapiv1.EVTSDKInstall, e.CmdID)
289 // Emit event remaining data in bufStdout/err
290 if len(s.bufStderr) > 0 || len(s.bufStdout) > 0 {
291 err := (*so).Emit(xsapiv1.EVTSDKInstall, xsapiv1.SDKManagementMsg{
293 Timestamp: time.Now().String(),
295 Progress: 50, // TODO add progress
301 s.Log.Errorf("WS Emit : %v", err)
308 if code == 0 && exitError == nil {
310 s.sdk.Status = xsapiv1.SdkStatusInstalled
312 s.sdk.LastError = "Installation failed (code " + strconv.Itoa(code) +
314 if exitError != nil {
315 s.sdk.LastError = ". Error: " + exitError.Error()
317 s.sdk.Status = xsapiv1.SdkStatusNotInstalled
321 if exitError != nil {
322 emitErr = exitError.Error()
324 if emitErr == "" && s.sdk.LastError != "" {
325 emitErr = s.sdk.LastError
329 errSoEmit := (*so).Emit(xsapiv1.EVTSDKInstall, xsapiv1.SDKManagementMsg{
331 Timestamp: time.Now().String(),
338 if errSoEmit != nil {
339 s.Log.Errorf("WS Emit : %v", errSoEmit)
342 // Cleanup command for the next time
346 // User data (used within callbacks)
347 data := make(map[string]interface{})
348 data["SDKID"] = s.sdk.ID
349 s.installCmd.UserData = &data
351 // Start command execution
352 s.Log.Infof("Install SDK %s: cmdID=%v, cmd=%v, args=%v", s.sdk.Name, s.installCmd.CmdID, s.installCmd.Cmd, s.installCmd.Args)
354 s.sdk.Status = xsapiv1.SdkStatusInstalling
357 err := s.installCmd.Start()
362 // AbortInstallRemove abort an install or remove command
363 func (s *CrossSDK) AbortInstallRemove(timeout int) error {
365 if s.installCmd == nil {
366 return fmt.Errorf("no installation in progress for this sdk")
369 s.sdk.Status = xsapiv1.SdkStatusNotInstalled
370 return s.installCmd.Signal("SIGKILL")
373 // Remove Used to remove/uninstall a SDK
374 func (s *CrossSDK) Remove(timeout int, sess *ClientSession) error {
376 if s.sdk.Status != xsapiv1.SdkStatusInstalled {
377 return fmt.Errorf("this sdk is not installed")
380 // IO socket can be nil when disconnected
381 so := s.sessions.IOSocketGet(sess.ID)
383 return fmt.Errorf("Cannot retrieve socket ")
386 s.sdk.Status = xsapiv1.SdkStatusUninstalling
389 if err := (*so).Emit(xsapiv1.EVTSDKStateChange, s.sdk); err != nil {
390 s.Log.Warningf("Cannot notify SDK remove: %v", err)
393 script := s.scripts[scriptRemove]
395 s.Log.Infof("Uninstall SDK %s: script=%v args=%v", s.sdk.Name, script, args)
397 cmd := exec.Command(script, args)
398 stdout, err := cmd.CombinedOutput()
400 s.sdk.Status = xsapiv1.SdkStatusNotInstalled
401 s.Log.Debugf("SDK uninstall err %v, output:\n %v", err, string(stdout))
406 evData := xsapiv1.SDKManagementMsg{
407 Timestamp: time.Now().String(),
414 if err := (*so).Emit(xsapiv1.EVTSDKRemove, evData); err != nil {
415 s.Log.Warningf("Cannot notify SDK remove end: %v", err)
418 return fmt.Errorf("Error while uninstalling sdk: %v", err)
422 evData := xsapiv1.SDKManagementMsg{
423 Timestamp: time.Now().String(),
430 if err := (*so).Emit(xsapiv1.EVTSDKRemove, evData); err != nil {
431 s.Log.Warningf("Cannot notify SDK remove end: %v", err)
437 // Get Return SDK definition
438 func (s *CrossSDK) Get() *xsapiv1.SDK {
442 // GetEnvCmd returns the command used to initialized the environment
443 func (s *CrossSDK) GetEnvCmd() []string {
444 return []string{"source", s.sdk.SetupFile}