Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/gitpod-cli/cmd/root.go
2498 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package cmd
6
7
import (
8
"context"
9
"encoding/json"
10
"fmt"
11
"io/ioutil"
12
"os"
13
"os/exec"
14
"os/signal"
15
"path/filepath"
16
"strings"
17
"syscall"
18
"time"
19
20
"github.com/go-errors/errors"
21
22
"github.com/gitpod-io/gitpod/gitpod-cli/pkg/gitpod"
23
"github.com/gitpod-io/gitpod/gitpod-cli/pkg/utils"
24
log "github.com/sirupsen/logrus"
25
"github.com/spf13/cobra"
26
"github.com/spf13/pflag"
27
28
ide_metrics "github.com/gitpod-io/gitpod/ide-metrics-api"
29
)
30
31
const (
32
rootCmdName = "gp"
33
)
34
35
type GpError struct {
36
Err error
37
Message string
38
OutCome string
39
ErrorCode string
40
ExitCode *int
41
Silence bool
42
}
43
44
func (e GpError) Error() string {
45
if e.Silence {
46
return ""
47
}
48
ret := e.Message
49
if ret != "" && e.Err != nil {
50
ret += ": "
51
}
52
if e.Err != nil {
53
ret += e.Err.Error()
54
}
55
return ret
56
}
57
58
func GetCommandName(path string) []string {
59
return strings.Fields(strings.TrimSpace(strings.TrimPrefix(path, rootCmdName)))
60
}
61
62
var lastSignal os.Signal
63
64
var rootCmd = &cobra.Command{
65
Use: rootCmdName,
66
SilenceErrors: true,
67
Short: "Command line interface for Gitpod",
68
PersistentPreRun: func(cmd *cobra.Command, args []string) {
69
cmd.SilenceUsage = true
70
cmdName := GetCommandName(cmd.CommandPath())
71
usedFlags := []string{}
72
flags := cmd.Flags()
73
flags.VisitAll(func(flag *pflag.Flag) {
74
if flag.Changed {
75
usedFlags = append(usedFlags, flag.Name)
76
}
77
})
78
utils.TrackCommandUsageEvent.Args = int64(len(args))
79
utils.TrackCommandUsageEvent.Command = cmdName
80
utils.TrackCommandUsageEvent.Flags = usedFlags
81
ctx, cancel := context.WithCancel(cmd.Context())
82
cmd.SetContext(ctx)
83
84
go func() {
85
signals := make(chan os.Signal, 1)
86
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
87
lastSignal = <-signals
88
cancel()
89
}()
90
},
91
}
92
93
var noColor bool
94
95
// Execute runs the root command
96
func Execute() {
97
entrypoint := strings.TrimPrefix(filepath.Base(os.Args[0]), "gp-")
98
for _, c := range rootCmd.Commands() {
99
if c.Name() == entrypoint {
100
// we can't call subcommands directly (they just call their parents - thanks cobra),
101
// so instead we have to manipulate the os.Args
102
os.Args = append([]string{os.Args[0], entrypoint}, os.Args[1:]...)
103
break
104
}
105
}
106
107
err := rootCmd.Execute()
108
109
file, ferr := os.OpenFile(os.TempDir()+"/gitpod-cli-errors.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
110
if ferr == nil {
111
log.SetOutput(file)
112
defer file.Close()
113
} else {
114
log.SetLevel(log.FatalLevel)
115
}
116
117
exitCode := 0
118
utils.TrackCommandUsageEvent.Outcome = utils.Outcome_Success
119
utils.TrackCommandUsageEvent.Duration = time.Since(time.UnixMilli(utils.TrackCommandUsageEvent.Timestamp)).Milliseconds()
120
121
if err != nil {
122
utils.TrackCommandUsageEvent.Outcome = utils.Outcome_SystemErr
123
exitCode = 1
124
if gpErr, ok := err.(GpError); ok {
125
if gpErr.OutCome != "" {
126
utils.TrackCommandUsageEvent.Outcome = gpErr.OutCome
127
}
128
if gpErr.ErrorCode != "" {
129
utils.TrackCommandUsageEvent.ErrorCode = gpErr.ErrorCode
130
}
131
if gpErr.ExitCode != nil {
132
exitCode = *gpErr.ExitCode
133
}
134
}
135
if utils.TrackCommandUsageEvent.ErrorCode == "" {
136
switch utils.TrackCommandUsageEvent.Outcome {
137
case utils.Outcome_UserErr:
138
utils.TrackCommandUsageEvent.ErrorCode = utils.UserErrorCode
139
case utils.Outcome_SystemErr:
140
utils.TrackCommandUsageEvent.ErrorCode = utils.SystemErrorCode
141
}
142
}
143
if utils.TrackCommandUsageEvent.Outcome == utils.Outcome_SystemErr {
144
errorReport(err)
145
}
146
}
147
148
sendAnalytics()
149
150
if err != nil {
151
fmt.Fprintln(os.Stderr, err)
152
os.Exit(exitCode)
153
}
154
if sig, ok := lastSignal.(syscall.Signal); ok {
155
os.Exit(128 + int(sig))
156
}
157
}
158
159
func sendAnalytics() {
160
if len(utils.TrackCommandUsageEvent.Command) == 0 {
161
return
162
}
163
data, err := utils.TrackCommandUsageEvent.ExportToJson()
164
if err != nil {
165
return
166
}
167
cmd := exec.Command(
168
"/.supervisor/supervisor",
169
"send-analytics",
170
"--event",
171
"gp_command",
172
"--data",
173
data,
174
)
175
cmd.Stdout = ioutil.Discard
176
cmd.Stderr = ioutil.Discard
177
178
// fire and release
179
err = cmd.Start()
180
if err != nil {
181
log.WithError(err).Error("cannot start send-analytics process")
182
return
183
}
184
if cmd.Process != nil {
185
_ = cmd.Process.Release()
186
}
187
}
188
189
func errorReport(err error) {
190
if err == nil {
191
return
192
}
193
reportErrorRequest := &ide_metrics.ReportErrorRequest{
194
ErrorStack: errors.New(err).ErrorStack(),
195
Component: "gitpod-cli",
196
Version: gitpod.Version,
197
}
198
199
payload, err := json.Marshal(reportErrorRequest)
200
if err != nil {
201
log.WithError(err).Error("failed to marshal json while attempting to report error")
202
return
203
}
204
205
if err != nil {
206
return
207
}
208
cmd := exec.Command(
209
"/.supervisor/supervisor",
210
"error-report",
211
"--data",
212
string(payload),
213
)
214
cmd.Stdout = ioutil.Discard
215
cmd.Stderr = ioutil.Discard
216
217
// fire and release
218
err = cmd.Start()
219
if err != nil {
220
log.WithError(err).Error("cannot start error-report process")
221
return
222
}
223
if cmd.Process != nil {
224
_ = cmd.Process.Release()
225
}
226
}
227
228