Added target and terminal support v1.1.0
authorSebastien Douheret <sebastien.douheret@iot.bzh>
Fri, 23 Feb 2018 21:13:22 +0000 (22:13 +0100)
committerSebastien Douheret <sebastien.douheret@iot.bzh>
Fri, 23 Feb 2018 21:13:22 +0000 (22:13 +0100)
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
.gitignore
.vscode/launch.json
.vscode/settings.json
cmd-projects.go
cmd-target.go [new file with mode: 0644]
glide.yaml
main.go
signals.go [new file with mode: 0644]
signals_other_arch.go [new file with mode: 0644]
signals_windows.go [new file with mode: 0644]
utils.go

index 2f4d998..27f9d12 100644 (file)
@@ -3,5 +3,6 @@ glide.lock
 bin/**
 tools/**
 vendor/**
+package/**
 *.zip
 __*
index f5ca868..1fc4ed1 100644 (file)
             },
             "args": ["-c", "xds-config-sample.env", "sdks", "ls"],
             "showLog": false
+        },
+        {
+            "name": "xds-cli (terminal)",
+            "type": "go",
+            "request": "launch",
+            "mode": "debug",
+            "program": "${workspaceRoot}",
+            "env": {
+                "GOPATH": "${workspaceRoot}/../../../../../..:${env:GOPATH}",
+                "XDS_APPNAME": "xds-cli",
+                "XDS_AGENT_URL": "localhost:8800",
+                "XDS_LOGLEVEL": "debug"
+            },
+            "args": ["targets", "term", "-tid", "10bd", "-u", "root" ],
+            //"args": ["targets", "term-rm", "e31ad288-18ab-11e8-8afa-3c970e49ad9b" ],
+            "showLog": false
         }
 
     ]
index d78342b..fc15f8b 100644 (file)
@@ -37,6 +37,8 @@
         "reflectme",
         "franciscocpg",
         "gerrit",
-        "EVTSDK"
+        "EVTSDK",
+        "tgts",
+        "sigs"
     ]
 }
index 1aa0dfc..e056563 100644 (file)
@@ -63,7 +63,7 @@ func initCmdProjects(cmdDef *[]cli.Command) {
                        },
                        {
                                Name:   "get",
-                               Usage:  "Get a property of a project",
+                               Usage:  "Get properties of a project",
                                Action: projectsGet,
                                Flags: []cli.Flag{
                                        cli.StringFlag{
@@ -180,12 +180,12 @@ func _displayProjects(prjs []xaapiv1.ProjectConfig, verbose bool) {
 func projectsAdd(ctx *cli.Context) error {
 
        // Decode project type
-       var ptype xaapiv1.ProjectType
+       var pType xaapiv1.ProjectType
        switch strings.ToLower(ctx.String("type")) {
        case "pathmap", "pm":
-               ptype = xaapiv1.TypePathMap
+               pType = xaapiv1.TypePathMap
        case "cloudsync", "cs":
-               ptype = xaapiv1.TypeCloudSync
+               pType = xaapiv1.TypeCloudSync
        default:
                return cli.NewExitError("Unknown project type", 1)
        }
@@ -193,7 +193,7 @@ func projectsAdd(ctx *cli.Context) error {
        prj := xaapiv1.ProjectConfig{
                ServerID:   XdsServerIDGet(),
                Label:      ctx.String("label"),
-               Type:       ptype,
+               Type:       pType,
                ClientPath: ctx.String("path"),
                ServerPath: ctx.String("server-path"),
        }
diff --git a/cmd-target.go b/cmd-target.go
new file mode 100644 (file)
index 0000000..688aef6
--- /dev/null
@@ -0,0 +1,546 @@
+/*
+ * Copyright (C) 2018 "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.
+ *
+ */
+
+package main
+
+import (
+       "bufio"
+       "encoding/json"
+       "fmt"
+       "os"
+       "sort"
+       "strings"
+       "syscall"
+       "time"
+
+       "github.com/golang/crypto/ssh/terminal"
+
+       "gerrit.automotivelinux.org/gerrit/src/xds/xds-agent.git/lib/xaapiv1"
+       "github.com/urfave/cli"
+)
+
+func initCmdTargets(cmdDef *[]cli.Command) {
+       *cmdDef = append(*cmdDef, cli.Command{
+               Name:     "targets",
+               Aliases:  []string{"tgt"},
+               HideHelp: true,
+               Usage:    "targets commands group",
+               Subcommands: []cli.Command{
+                       {
+                               Name:    "add",
+                               Aliases: []string{"a"},
+                               Usage:   "Add a new target",
+                               Action:  targetsAdd,
+                               Flags: []cli.Flag{
+                                       cli.StringFlag{
+                                               Name:  "name, n",
+                                               Usage: "target name (free form string)",
+                                       },
+                                       cli.StringFlag{
+                                               Name:  "ip",
+                                               Usage: "IP address",
+                                       },
+                                       cli.BoolFlag{
+                                               Name:  "short, s",
+                                               Usage: "short output, only print create target id (useful from scripting)",
+                                       },
+                                       cli.StringFlag{
+                                               Name:  "type, t",
+                                               Usage: "target type (standard|std)",
+                                       },
+                               },
+                       },
+                       {
+                               Name:   "get",
+                               Usage:  "Get properties of a target",
+                               Action: targetsGet,
+                               Flags: []cli.Flag{
+                                       cli.StringFlag{
+                                               Name:   "id",
+                                               Usage:  "target id",
+                                               EnvVar: "XDS_TARGET_ID",
+                                       },
+                               },
+                       },
+                       {
+                               Name:    "list",
+                               Aliases: []string{"ls"},
+                               Usage:   "List existing targets",
+                               Action:  targetsList,
+                               Flags: []cli.Flag{
+                                       cli.BoolFlag{
+                                               Name:  "verbose, v",
+                                               Usage: "display verbose output",
+                                       },
+                               },
+                       },
+                       {
+                               Name:    "remove",
+                               Aliases: []string{"rm"},
+                               Usage:   "Remove an existing target",
+                               Action:  targetsRemove,
+                               Flags: []cli.Flag{
+                                       cli.StringFlag{
+                                               Name:   "id",
+                                               Usage:  "target id",
+                                               EnvVar: "XDS_TARGET_ID",
+                                       },
+                                       cli.BoolFlag{
+                                               Name:  "force, f",
+                                               Usage: "remove confirmation prompt before removal",
+                                       },
+                               },
+                       },
+                       {
+                               Name:    "terminal",
+                               Aliases: []string{"term"},
+                               Usage:   "Open a target terminal",
+                               Action:  terminalOpen,
+                               Flags: []cli.Flag{
+                                       cli.StringFlag{
+                                               Name:   "id",
+                                               Usage:  "target id",
+                                               EnvVar: "XDS_TARGET_ID",
+                                       },
+                                       cli.StringSliceFlag{
+                                               Name:  "options, o",
+                                               Usage: "passthrough options set to command line used to start terminal",
+                                       },
+                                       cli.StringFlag{
+                                               Name:   "termId, tid",
+                                               Usage:  "terminal id",
+                                               EnvVar: "XDS_TERMINAL_ID",
+                                       },
+                                       cli.StringFlag{
+                                               Name:   "user, u",
+                                               Usage:  "user name used to connect terminal",
+                                               EnvVar: "XDS_TERMINAL_USER",
+                                       },
+                               },
+                       },
+                       {
+                               Name:    "terminal-remove",
+                               Aliases: []string{"term-rm"},
+                               Usage:   "Remove a target terminal",
+                               Action:  terminalRemove,
+                               Flags: []cli.Flag{
+                                       cli.StringFlag{
+                                               Name:   "id",
+                                               Usage:  "target id",
+                                               EnvVar: "XDS_TARGET_ID",
+                                       },
+                                       cli.StringFlag{
+                                               Name:   "termId, tid",
+                                               Usage:  "terminal id",
+                                               EnvVar: "XDS_TERMINAL_ID",
+                                       },
+                               },
+                       },
+               },
+       })
+}
+
+func targetsList(ctx *cli.Context) error {
+       // Get targets list
+       tgts := []xaapiv1.TargetConfig{}
+       if err := TargetsListGet(&tgts); err != nil {
+               return cli.NewExitError(err.Error(), 1)
+       }
+       _displayTargets(tgts, ctx.Bool("verbose"))
+       return nil
+}
+
+func targetsGet(ctx *cli.Context) error {
+       id := GetID(ctx)
+       if id == "" {
+               return cli.NewExitError("id parameter or option must be set", 1)
+       }
+       tgts := make([]xaapiv1.TargetConfig, 1)
+       url := XdsServerComputeURL("/targets/" + id)
+       if err := HTTPCli.Get(url, &tgts[0]); err != nil {
+               return cli.NewExitError(err, 1)
+       }
+       _displayTargets(tgts, true)
+       return nil
+}
+
+func _displayTargets(tgts []xaapiv1.TargetConfig, verbose bool) {
+       // Display result
+       first := true
+       writer := NewTableWriter()
+       for _, tgt := range tgts {
+               if verbose {
+                       if !first {
+                               fmt.Fprintln(writer)
+                       }
+                       fmt.Fprintln(writer, "ID:\t", tgt.ID)
+                       fmt.Fprintln(writer, "Name:\t", tgt.Name)
+                       fmt.Fprintln(writer, "Type:\t", tgt.Type)
+                       fmt.Fprintln(writer, "IP:\t", tgt.IP)
+                       fmt.Fprintln(writer, "Status:\t", tgt.Status)
+                       if len(tgt.Terms) > 0 {
+                               tmNfo := "\t\n"
+                               for _, tt := range tgt.Terms {
+                                       tmNfo += "\t ID:\t" + tt.ID + "\n"
+                                       tmNfo += "\t  Name:\t" + tt.Name + "\n"
+                                       tmNfo += "\t  Status:\t" + tt.Status + "\n"
+                                       tmNfo += "\t  User:\t" + tt.User + "\n"
+                                       tmNfo += "\t  Options:\t" + strings.Join(tt.Options, " ") + "\n"
+                                       tmNfo += fmt.Sprintf("\t  Size:\t%v x %v\n", tt.Cols, tt.Rows)
+                               }
+                               fmt.Fprintln(writer, "Terminals:", tmNfo)
+                       } else {
+                               fmt.Fprintln(writer, "Terminals:\t None")
+                       }
+
+               } else {
+                       if first {
+                               fmt.Fprintln(writer, "ID\t Name\t IP\t Terminals #")
+                       }
+                       fmt.Fprintln(writer, tgt.ID[0:8], "\t", tgt.Name, "\t", tgt.IP, "\t", len(tgt.Terms))
+               }
+               first = false
+       }
+       writer.Flush()
+}
+
+func targetsAdd(ctx *cli.Context) error {
+
+       // Decode target type
+       var tType xaapiv1.TargetType
+       switch strings.ToLower(ctx.String("type")) {
+       case "standard", "std":
+               tType = xaapiv1.TypeTgtStandard
+       default:
+               tType = xaapiv1.TypeTgtStandard
+       }
+
+       tgt := xaapiv1.TargetConfig{
+               Name: ctx.String("name"),
+               Type: tType,
+               IP:   ctx.String("ip"),
+       }
+
+       Log.Infof("POST /target %v", tgt)
+       newTgt := xaapiv1.TargetConfig{}
+       err := HTTPCli.Post(XdsServerComputeURL("/targets"), tgt, &newTgt)
+       if err != nil {
+               return cli.NewExitError(err, 1)
+       }
+
+       if ctx.Bool("short") {
+               fmt.Println(newTgt.ID)
+       } else {
+               fmt.Printf("New target '%s' (id %v) successfully created.\n", newTgt.Name, newTgt.ID)
+       }
+
+       return nil
+}
+
+func targetsRemove(ctx *cli.Context) error {
+       var res xaapiv1.TargetConfig
+       id := GetID(ctx)
+       if id == "" {
+               return cli.NewExitError("id parameter or option must be set", 1)
+       }
+
+       if !ctx.Bool("force") {
+               if !Confirm("Do you permanently remove target id '" + id + "' [yes/No] ? ") {
+                       return nil
+               }
+       }
+
+       if err := HTTPCli.Delete(XdsServerComputeURL("/targets/"+id), &res); err != nil {
+               return cli.NewExitError(err, 1)
+       }
+
+       fmt.Println("Target ID " + res.ID + " successfully deleted.")
+       return nil
+}
+
+func terminalOpen(ctx *cli.Context) error {
+
+       tgt, term, err := GetTargetAndTerminalIDs(ctx, true)
+       if err != nil {
+               return cli.NewExitError(err.Error(), 1)
+       }
+       if tgt == nil {
+               return cli.NewExitError("cannot identify target", 1)
+       }
+
+       if term == nil {
+               // Create a new terminal when needed
+               newTerm := xaapiv1.TerminalConfig{
+                       Name:    "ssh session from xds-cli",
+                       Type:    xaapiv1.TypeTermSSH,
+                       User:    ctx.String("user"),
+                       Options: ctx.StringSlice("options"),
+               }
+               term = &newTerm
+               url := XdsServerComputeURL("/targets/" + tgt.ID + "/terminals")
+               if err := HTTPCli.Post(url, &newTerm, term); err != nil {
+                       return cli.NewExitError(err.Error(), 1)
+               }
+               Log.Debugf("New terminal created: %v", term)
+       } else {
+               // Update terminal config when needed
+               needUp := false
+               if ctx.String("user") != "" {
+                       term.User = ctx.String("user")
+                       needUp = true
+               }
+               if len(ctx.StringSlice("options")) > 0 {
+                       term.Options = ctx.StringSlice("options")
+                       needUp = true
+               }
+               if needUp {
+                       url := XdsServerComputeURL("/targets/" + tgt.ID + "/terminals/" + term.ID)
+                       if err := HTTPCli.Put(url, &term, term); err != nil {
+                               return cli.NewExitError(err.Error(), 1)
+                       }
+                       Log.Debugf("Update terminal config: %v", term)
+               }
+       }
+
+       // Process Socket IO events
+       type exitResult struct {
+               error error
+               code  int
+       }
+       exitChan := make(chan exitResult, 1)
+
+       IOsk.On("disconnection", func(err error) {
+               Log.Debugf("WS disconnection event with err: %v\n", err)
+               exitChan <- exitResult{err, 2}
+       })
+
+       IOsk.On(xaapiv1.TerminalOutEvent, func(ev xaapiv1.TerminalOutMsg) {
+               if ev.Stdout != "" {
+                       fmt.Printf(ev.Stdout)
+               }
+               if ev.Stderr != "" {
+                       fmt.Fprintf(os.Stderr, ev.Stderr)
+               }
+       })
+
+       IOsk.On(xaapiv1.TerminalExitEvent, func(ev xaapiv1.TerminalExitMsg) {
+               exitChan <- exitResult{ev.Error, ev.Code}
+       })
+
+       /* FIXME - use raw mode to support escape keys, arrows keys, control char...
+       // import       "github.com/golang/crypto/ssh/terminal"
+
+       oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
+       if err == nil {
+               defer terminal.Restore(int(os.Stdin.Fd()), oldState)
+       }
+       */
+
+       // Send stdin though WS
+       go func() {
+               paranoia := 600
+               reader := bufio.NewReader(os.Stdin)
+               for {
+                       sc := bufio.NewScanner(reader)
+                       for sc.Scan() {
+                               command := sc.Text()
+                               Log.Debugf("Terminal Send command <%v>", command)
+                               IOsk.Emit(xaapiv1.TerminalInEvent, command+"\n")
+                       }
+                       if sc.Err() != nil {
+                               exitChan <- exitResult{sc.Err(), 3}
+                       }
+
+                       // CTRL-D exited scanner, so send it explicitly
+                       IOsk.Emit(xaapiv1.TerminalInEvent, "\x04\n")
+                       time.Sleep(time.Millisecond * 100)
+
+                       if paranoia--; paranoia <= 0 {
+                               msg := "Abnormal loop detected on stdin"
+                               Log.Errorf("Abnormal loop detected on stdin")
+
+                               // Send signal to gently exit terminal session
+                               TerminalSendSignal(tgt, term, syscall.SIGTERM)
+
+                               exitChan <- exitResult{fmt.Errorf(msg), int(syscall.ELOOP)}
+                       }
+               }
+       }()
+
+       // Handle signals
+       err = OnSignals(func(sig os.Signal) {
+               Log.Debugf("Send signal %v", sig)
+               if IsWinResizeSignal(sig) {
+                       TerminalResize(tgt, term)
+               } else if IsInterruptSignal(sig) {
+                       IOsk.Emit(xaapiv1.TerminalInEvent, "\x03\n")
+               } else {
+                       TerminalSendSignal(tgt, term, sig)
+               }
+       })
+       if err != nil {
+               return cli.NewExitError(err.Error(), 1)
+       }
+
+       // Send open command
+       url := XdsServerComputeURL("/targets/" + tgt.ID + "/terminals/" + term.ID + "/open")
+       LogPost("POST %v", url)
+       if err := HTTPCli.Post(url, nil, term); err != nil {
+               return cli.NewExitError(err.Error(), 1)
+       }
+
+       // Wait exit - blocking
+       select {
+       case res := <-exitChan:
+               errStr := ""
+               if res.code == 0 {
+                       Log.Debugln("Exit Target Terminal successfully")
+               }
+               if res.error != nil {
+                       Log.Debugln("Exit Target Terminal with ERROR: ", res.error.Error())
+                       errStr = res.error.Error()
+               }
+               return cli.NewExitError(errStr, res.code)
+       }
+}
+
+func terminalRemove(ctx *cli.Context) error {
+
+       tgt, term, err := GetTargetAndTerminalIDs(ctx, false)
+       if err != nil {
+               return cli.NewExitError(err.Error(), 1)
+       }
+       if tgt == nil || tgt.ID == "" {
+               return cli.NewExitError("cannot identify target id", 1)
+       }
+       if term == nil || term.ID == "" {
+               return cli.NewExitError("cannot identify terminal id", 1)
+       }
+
+       // Send delete command
+       url := XdsServerComputeURL("/targets/" + tgt.ID + "/terminals/" + term.ID)
+       LogPost("DELETE %v", url)
+       if err := HTTPCli.Delete(url, term); err != nil {
+               return cli.NewExitError(err.Error(), 1)
+       }
+
+       return nil
+}
+
+/**
+ * utils functions
+ */
+
+// TerminalResize Send command to resize target terminal
+func TerminalResize(tgt *xaapiv1.TargetConfig, term *xaapiv1.TerminalConfig) {
+       col, row, err := terminal.GetSize(int(os.Stdin.Fd()))
+       if err != nil {
+               Log.Errorf("Error cannot get terminal size: %v", err)
+       }
+       Log.Debugf("Terminal resizing rows %v, cols %v", row, col)
+       sz := xaapiv1.TerminalResizeArgs{Rows: uint16(row), Cols: uint16(col)}
+       url := XdsServerComputeURL("/targets/" + tgt.ID + "/terminals/" + term.ID + "/resize")
+       if err := HTTPCli.Post(url, &sz, nil); err != nil {
+               Log.Errorf("Error while resizing terminal (term %v): %v", sz, err)
+       }
+}
+
+// TerminalSendSignal Send a signal to a target terminal
+func TerminalSendSignal(tgt *xaapiv1.TargetConfig, term *xaapiv1.TerminalConfig, sig os.Signal) {
+       url := XdsServerComputeURL("/targets/" + tgt.ID + "/terminals/" + term.ID + "/signal/" + sig.String())
+       if err := HTTPCli.Post(url, nil, nil); err != nil {
+               Log.Errorf("Error to send signal %v: %v", sig, err)
+       }
+}
+
+// GetTargetAndTerminalIDs Retrieve Target and Terminal definition from IDs
+func GetTargetAndTerminalIDs(ctx *cli.Context, useFirstFree bool) (*xaapiv1.TargetConfig, *xaapiv1.TerminalConfig, error) {
+
+       idArg := ctx.String("id")
+       tidArg := GetIDName(ctx, "termId")
+       if tidArg == "" {
+               tidArg = GetIDName(ctx, "tid")
+       }
+       if idArg == "" && tidArg == "" {
+               return nil, nil, fmt.Errorf("id or termId argument must be set")
+       }
+
+       tgts := []xaapiv1.TargetConfig{}
+       if err := TargetsListGet(&tgts); err != nil {
+               return nil, nil, err
+       }
+
+       matching := 0
+       ti := 0
+       tj := 0
+       for ii, tt := range tgts {
+               for jj, ttm := range tt.Terms {
+                       if idArg == "" && compareID(ttm.ID, tidArg) {
+                               ti = ii
+                               tj = jj
+                               matching++
+                       }
+                       if idArg != "" && compareID(tt.ID, idArg) && compareID(ttm.ID, tidArg) {
+                               ti = ii
+                               tj = jj
+                               matching++
+                       }
+               }
+       }
+       if matching > 1 {
+               return nil, nil, fmt.Errorf("Multiple IDs found, please set -id and -tid with full ID notation")
+       } else if matching == 1 {
+               return &tgts[ti], &tgts[ti].Terms[tj], nil
+       }
+
+       // Allow to create a new terminal when only target id is set
+       idArg = GetIDName(ctx, "id")
+       if idArg != "" {
+               for _, tt := range tgts {
+                       if compareID(tt.ID, idArg) {
+                               return &tt, nil, nil
+                       }
+               }
+       }
+
+       return nil, nil, fmt.Errorf("No matching id found")
+}
+
+// Sort targets by Name
+type _TgtByName []xaapiv1.TargetConfig
+
+func (s _TgtByName) Len() int           { return len(s) }
+func (s _TgtByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+func (s _TgtByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
+
+// TargetsListGet Get the list of existing targets
+func TargetsListGet(tgts *[]xaapiv1.TargetConfig) error {
+       var data []byte
+       if err := HTTPCli.HTTPGet(XdsServerComputeURL("/targets"), &data); err != nil {
+               return err
+       }
+       Log.Debugf("Result of /targets: %v", string(data[:]))
+
+       if err := json.Unmarshal(data, &tgts); err != nil {
+               return err
+       }
+
+       sort.Sort(_TgtByName(*tgts))
+
+       return nil
+}
index cf23f36..a0e7826 100644 (file)
@@ -12,11 +12,11 @@ import:
 - package: github.com/sebd71/go-socket.io-client
   version: 46defcb47f
 - package: gerrit.automotivelinux.org/gerrit/src/xds/xds-agent.git
-  version: ~1.0.0
+  version: ~1.1.0
   subpackages:
   - lib/xaapiv1
 - package: gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git
-  version: ^0.1.0
+  version: ~0.2.0
   subpackages:
   - golib/common
 - package: github.com/joho/godotenv
@@ -25,3 +25,4 @@ import:
   - cmd/godotenv
 - package: github.com/franciscocpg/reflectme
   version: ^0.1.9
+- package: github.com/golang/crypto/ssh/terminal
diff --git a/main.go b/main.go
index f9b32f3..7d1f5e1 100644 (file)
--- a/main.go
+++ b/main.go
@@ -208,6 +208,7 @@ func main() {
        initCmdProjects(&app.Commands)
        initCmdSdks(&app.Commands)
        initCmdExec(&app.Commands)
+       initCmdTargets(&app.Commands)
        initCmdMisc(&app.Commands)
 
        // Add --config option to all commands to support --config option either before or after command verb
@@ -312,6 +313,10 @@ func main() {
                XdsConnClose()
        }()
 
+       // Start signals monitoring routine
+       MonitorSignals()
+
+       // Run the cli app
        app.Run(os.Args)
 }
 
diff --git a/signals.go b/signals.go
new file mode 100644 (file)
index 0000000..042257e
--- /dev/null
@@ -0,0 +1,70 @@
+package main
+
+import (
+       "fmt"
+       "os"
+       "os/signal"
+)
+
+// OnSignalCB callback type for signal
+type OnSignalCB func(sig os.Signal)
+
+// MonSignals .
+type MonSignals struct {
+       callback          map[string][]OnSignalCB
+       registeredSignals []os.Signal
+}
+
+var monSig MonSignals
+
+// MonitorSignals Routine used to monitor signals (eg. SIGINT, SIGTERM, ...)
+func MonitorSignals() {
+
+       monSig.callback = make(map[string][]OnSignalCB)
+       monSig.registeredSignals = GetRegisteredSignals()
+
+       sigs := make(chan os.Signal, 1)
+       signal.Notify(sigs, monSig.registeredSignals...)
+
+       go func() {
+               for {
+                       sig := <-sigs
+                       Log.Debugf("Detect signal %v", sig)
+                       if cbArr, exist := monSig.callback[sig.String()]; exist {
+                               for _, cb := range cbArr {
+                                       cb(sig)
+                               }
+                       }
+               }
+       }()
+}
+
+// isSupportSignal Check if a signal is supported or not
+func isSupportSignal(sig os.Signal) bool {
+       for _, s := range monSig.registeredSignals {
+               if s == sig {
+                       return true
+               }
+       }
+       return false
+}
+
+// OnSignal Register a callback for a specified signal
+func OnSignal(sig os.Signal, fCB OnSignalCB) error {
+       if !isSupportSignal(sig) {
+               return fmt.Errorf("unsupported signal %v", sig)
+       }
+       sigStr := sig.String()
+       monSig.callback[sigStr] = append(monSig.callback[sigStr], fCB)
+       return nil
+}
+
+// OnSignals Register a callback for any signals
+func OnSignals(fCB OnSignalCB) error {
+       for _, s := range monSig.registeredSignals {
+               if err := OnSignal(s, fCB); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
diff --git a/signals_other_arch.go b/signals_other_arch.go
new file mode 100644 (file)
index 0000000..dd5a658
--- /dev/null
@@ -0,0 +1,45 @@
+// +build !windows
+
+/*
+ * Copyright (C) 2017-2018 "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.
+ *
+ */
+
+package main
+
+import (
+       "os"
+       "syscall"
+)
+
+// GetRegisteredSignals Return the list of registrable signals
+func GetRegisteredSignals() []os.Signal {
+       return []os.Signal{
+               syscall.SIGTERM,
+               syscall.SIGINT,
+               syscall.SIGWINCH,
+       }
+}
+
+// IsWinResizeSignal Return true if SIGWINCH signal
+func IsWinResizeSignal(sig os.Signal) bool {
+       return sig == syscall.SIGWINCH
+}
+
+// IsInterruptSignal Return true if SIGINT signal
+func IsInterruptSignal(sig os.Signal) bool {
+       return sig == syscall.SIGINT
+}
diff --git a/signals_windows.go b/signals_windows.go
new file mode 100644 (file)
index 0000000..517220a
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017-2018 "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.
+ *
+ */
+
+package main
+
+import (
+       "os"
+       "syscall"
+)
+
+// GetRegisteredSignals Return the list of registrable signals
+func GetRegisteredSignals() []os.Signal {
+       return []os.Signal{
+               syscall.SIGTERM,
+               syscall.SIGINT,
+       }
+}
+
+// IsWinResizeSignal Return true if SIGWINCH signal
+func IsWinResizeSignal(sig os.Signal) bool {
+       // Not supported/implemented yet
+       return false
+}
+
+// IsInterruptSignal Return true if SIGINT signal
+func IsInterruptSignal(sig os.Signal) bool {
+       return sig == syscall.SIGINT
+}
index afa0942..0ca6471 100644 (file)
--- a/utils.go
+++ b/utils.go
@@ -123,7 +123,15 @@ func LogPost(format string, data interface{}) {
 
 // GetID Return a string ID set with --id option or as simple parameter
 func GetID(ctx *cli.Context) string {
-       id := ctx.String("id")
+       return GetIDName(ctx, "id")
+}
+
+// GetIDName Return a string ID set with --XXX option or as simple parameter
+func GetIDName(ctx *cli.Context, idName string) string {
+       if idName == "" {
+               return ""
+       }
+       id := ctx.String(idName)
        idArgs := ctx.Args().First()
        if id == "" && idArgs != "" {
                id = idArgs
@@ -139,3 +147,8 @@ func Confirm(question string) bool {
        ans := strings.ToLower(strings.TrimSpace(answer))
        return (ans == "y" || ans == "yes")
 }
+
+// compareID Compare an ID to a reference ID
+func compareID(refID, ID string) bool {
+       return refID != "" && ID != "" && strings.Contains(refID, ID)
+}