Initial commit
[src/xds/xds-cli.git] / cmd-exec.go
1 package main
2
3 import (
4         "fmt"
5         "os"
6         "strings"
7
8         "github.com/iotbzh/xds-agent/lib/apiv1"
9         common "github.com/iotbzh/xds-common/golib"
10         "github.com/joho/godotenv"
11         "github.com/urfave/cli"
12 )
13
14 func initCmdExec(cmdDef *[]cli.Command) {
15         *cmdDef = append(*cmdDef, cli.Command{
16                 Name:   "exec",
17                 Usage:  "execute a command in XDS",
18                 Action: exec,
19                 Flags: []cli.Flag{
20                         cli.StringFlag{
21                                 Name:   "id",
22                                 EnvVar: "XDS_PROJECT_ID",
23                                 Usage:  "project ID you want to build (mandatory variable)",
24                         },
25                         cli.StringFlag{
26                                 Name:   "rpath",
27                                 EnvVar: "XDS_RPATH",
28                                 Usage:  "relative path into project",
29                         },
30                         cli.StringFlag{
31                                 Name:   "sdkid",
32                                 EnvVar: "XDS_SDK_ID",
33                                 Usage:  "Cross Sdk ID to use to build project",
34                         },
35                 },
36         })
37 }
38
39 func exec(ctx *cli.Context) error {
40         prjID := ctx.String("id")
41         confFile := ctx.String("config")
42         rPath := ctx.String("rPath")
43         sdkid := ctx.String("sdkid")
44
45         // Check mandatory args
46         if prjID == "" {
47                 return cli.NewExitError("project id must be set (see --id option)", 1)
48         }
49
50         // Load config file if requested
51         envMap := make(map[string]string)
52         if confFile != "" {
53                 if !common.Exists(confFile) {
54                         exitError(1, "Error env config file not found")
55                 }
56                 // Load config file variables that will overwrite env variables
57                 err := godotenv.Overload(confFile)
58                 if err != nil {
59                         exitError(1, "Error loading env config file "+confFile)
60                 }
61                 envMap, err = godotenv.Read(confFile)
62                 if err != nil {
63                         exitError(1, "Error reading env config file "+confFile)
64                 }
65         }
66
67         argsCommand := make([]string, len(ctx.Args()))
68         copy(argsCommand, ctx.Args())
69         Log.Infof("Execute: /exec %v", argsCommand)
70
71         // Log useful info for debugging
72         ver := apiv1.XDSVersion{}
73         XdsVersionGet(&ver)
74         Log.Infof("XDS version: %v", ver)
75
76         // Process Socket IO events
77         type exitResult struct {
78                 error error
79                 code  int
80         }
81         exitChan := make(chan exitResult, 1)
82
83         IOsk.On("disconnection", func(err error) {
84                 exitChan <- exitResult{err, 2}
85         })
86
87         outFunc := func(timestamp, stdout, stderr string) {
88                 tm := ""
89                 if ctx.Bool("WithTimestamp") {
90                         tm = timestamp + "| "
91                 }
92                 if stdout != "" {
93                         fmt.Printf("%s%s", tm, stdout)
94                 }
95                 if stderr != "" {
96                         fmt.Fprintf(os.Stderr, "%s%s", tm, stderr)
97                 }
98         }
99
100         IOsk.On(apiv1.ExecOutEvent, func(ev apiv1.ExecOutMsg) {
101                 outFunc(ev.Timestamp, ev.Stdout, ev.Stderr)
102         })
103
104         IOsk.On(apiv1.ExecExitEvent, func(ev apiv1.ExecExitMsg) {
105                 exitChan <- exitResult{ev.Error, ev.Code}
106         })
107
108         // Retrieve the project definition
109         prj := apiv1.ProjectConfig{}
110         if err := HTTPCli.Get("/projects/"+prjID, &prj); err != nil {
111                 return cli.NewExitError(err, 1)
112         }
113
114         // Auto setup rPath if needed
115         if rPath == "" {
116                 cwd, err := os.Getwd()
117                 if err == nil {
118                         fldRp := prj.ClientPath
119                         if !strings.HasPrefix(fldRp, "/") {
120                                 fldRp = "/" + fldRp
121                         }
122                         Log.Debugf("Try to auto-setup rPath: cwd=%s ; ClientPath=%s", cwd, fldRp)
123                         if sp := strings.SplitAfter(cwd, fldRp); len(sp) == 2 {
124                                 rPath = strings.Trim(sp[1], "/")
125                                 Log.Debugf("Auto-setup rPath to: '%s'", rPath)
126                         }
127                 }
128         }
129
130         // Build env
131         Log.Debugf("Command env: %v", envMap)
132         env := []string{}
133         for k, v := range envMap {
134                 env = append(env, k+"="+v)
135         }
136
137         // Send build command
138         args := apiv1.ExecArgs{
139                 ID:         prjID,
140                 SdkID:      sdkid,
141                 Cmd:        strings.Trim(argsCommand[0], " "),
142                 Args:       argsCommand[1:],
143                 Env:        env,
144                 RPath:      rPath,
145                 CmdTimeout: 60,
146         }
147
148         LogPost("POST /exec %v", args)
149         if err := HTTPCli.Post("/exec", args, nil); err != nil {
150                 return cli.NewExitError(err.Error(), 1)
151         }
152
153         // Wait exit
154         select {
155         case res := <-exitChan:
156                 errStr := ""
157                 if res.code == 0 {
158                         Log.Debugln("Exit successfully")
159                 }
160                 if res.error != nil {
161                         Log.Debugln("Exit with ERROR: ", res.error.Error())
162                         errStr = res.error.Error()
163                 }
164                 return cli.NewExitError(errStr, res.code)
165         }
166 }