aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2018-02-23 22:13:22 +0100
committerSebastien Douheret <sebastien.douheret@iot.bzh>2018-02-23 22:13:22 +0100
commit2231e3eae86c5e3ae05e30f667d331f5875c7884 (patch)
tree44c56c9d8b3bb4c6cbee26bedc2c0aec9d9bb5f1
parent84273d6128b7c3c9d31c2f3a1453107a78e949a9 (diff)
Added target and terminal supportv1.1.0
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
-rw-r--r--.gitignore1
-rw-r--r--.vscode/launch.json16
-rw-r--r--.vscode/settings.json4
-rw-r--r--cmd-projects.go10
-rw-r--r--cmd-target.go546
-rw-r--r--glide.yaml5
-rw-r--r--main.go5
-rw-r--r--signals.go70
-rw-r--r--signals_other_arch.go45
-rw-r--r--signals_windows.go43
-rw-r--r--utils.go15
11 files changed, 751 insertions, 9 deletions
diff --git a/.gitignore b/.gitignore
index 2f4d998..27f9d12 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,6 @@ glide.lock
bin/**
tools/**
vendor/**
+package/**
*.zip
__*
diff --git a/.vscode/launch.json b/.vscode/launch.json
index f5ca868..1fc4ed1 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -81,6 +81,22 @@
},
"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
}
]
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d78342b..fc15f8b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -37,6 +37,8 @@
"reflectme",
"franciscocpg",
"gerrit",
- "EVTSDK"
+ "EVTSDK",
+ "tgts",
+ "sigs"
]
}
diff --git a/cmd-projects.go b/cmd-projects.go
index 1aa0dfc..e056563 100644
--- a/cmd-projects.go
+++ b/cmd-projects.go
@@ -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
index 0000000..688aef6
--- /dev/null
+++ b/cmd-target.go
@@ -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
+}
diff --git a/glide.yaml b/glide.yaml
index cf23f36..a0e7826 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -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
--- 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
index 0000000..042257e
--- /dev/null
+++ b/signals.go
@@ -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
index 0000000..dd5a658
--- /dev/null
+++ b/signals_other_arch.go
@@ -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
index 0000000..517220a
--- /dev/null
+++ b/signals_windows.go
@@ -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
+}
diff --git a/utils.go b/utils.go
index afa0942..0ca6471 100644
--- 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)
+}