package cmd
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/go-errors/errors"
"github.com/gitpod-io/gitpod/gitpod-cli/pkg/gitpod"
"github.com/gitpod-io/gitpod/gitpod-cli/pkg/utils"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
ide_metrics "github.com/gitpod-io/gitpod/ide-metrics-api"
)
const (
rootCmdName = "gp"
)
type GpError struct {
Err error
Message string
OutCome string
ErrorCode string
ExitCode *int
Silence bool
}
func (e GpError) Error() string {
if e.Silence {
return ""
}
ret := e.Message
if ret != "" && e.Err != nil {
ret += ": "
}
if e.Err != nil {
ret += e.Err.Error()
}
return ret
}
func GetCommandName(path string) []string {
return strings.Fields(strings.TrimSpace(strings.TrimPrefix(path, rootCmdName)))
}
var lastSignal os.Signal
var rootCmd = &cobra.Command{
Use: rootCmdName,
SilenceErrors: true,
Short: "Command line interface for Gitpod",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
cmd.SilenceUsage = true
cmdName := GetCommandName(cmd.CommandPath())
usedFlags := []string{}
flags := cmd.Flags()
flags.VisitAll(func(flag *pflag.Flag) {
if flag.Changed {
usedFlags = append(usedFlags, flag.Name)
}
})
utils.TrackCommandUsageEvent.Args = int64(len(args))
utils.TrackCommandUsageEvent.Command = cmdName
utils.TrackCommandUsageEvent.Flags = usedFlags
ctx, cancel := context.WithCancel(cmd.Context())
cmd.SetContext(ctx)
go func() {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
lastSignal = <-signals
cancel()
}()
},
}
var noColor bool
func Execute() {
entrypoint := strings.TrimPrefix(filepath.Base(os.Args[0]), "gp-")
for _, c := range rootCmd.Commands() {
if c.Name() == entrypoint {
os.Args = append([]string{os.Args[0], entrypoint}, os.Args[1:]...)
break
}
}
err := rootCmd.Execute()
file, ferr := os.OpenFile(os.TempDir()+"/gitpod-cli-errors.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if ferr == nil {
log.SetOutput(file)
defer file.Close()
} else {
log.SetLevel(log.FatalLevel)
}
exitCode := 0
utils.TrackCommandUsageEvent.Outcome = utils.Outcome_Success
utils.TrackCommandUsageEvent.Duration = time.Since(time.UnixMilli(utils.TrackCommandUsageEvent.Timestamp)).Milliseconds()
if err != nil {
utils.TrackCommandUsageEvent.Outcome = utils.Outcome_SystemErr
exitCode = 1
if gpErr, ok := err.(GpError); ok {
if gpErr.OutCome != "" {
utils.TrackCommandUsageEvent.Outcome = gpErr.OutCome
}
if gpErr.ErrorCode != "" {
utils.TrackCommandUsageEvent.ErrorCode = gpErr.ErrorCode
}
if gpErr.ExitCode != nil {
exitCode = *gpErr.ExitCode
}
}
if utils.TrackCommandUsageEvent.ErrorCode == "" {
switch utils.TrackCommandUsageEvent.Outcome {
case utils.Outcome_UserErr:
utils.TrackCommandUsageEvent.ErrorCode = utils.UserErrorCode
case utils.Outcome_SystemErr:
utils.TrackCommandUsageEvent.ErrorCode = utils.SystemErrorCode
}
}
if utils.TrackCommandUsageEvent.Outcome == utils.Outcome_SystemErr {
errorReport(err)
}
}
sendAnalytics()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(exitCode)
}
if sig, ok := lastSignal.(syscall.Signal); ok {
os.Exit(128 + int(sig))
}
}
func sendAnalytics() {
if len(utils.TrackCommandUsageEvent.Command) == 0 {
return
}
data, err := utils.TrackCommandUsageEvent.ExportToJson()
if err != nil {
return
}
cmd := exec.Command(
"/.supervisor/supervisor",
"send-analytics",
"--event",
"gp_command",
"--data",
data,
)
cmd.Stdout = ioutil.Discard
cmd.Stderr = ioutil.Discard
err = cmd.Start()
if err != nil {
log.WithError(err).Error("cannot start send-analytics process")
return
}
if cmd.Process != nil {
_ = cmd.Process.Release()
}
}
func errorReport(err error) {
if err == nil {
return
}
reportErrorRequest := &ide_metrics.ReportErrorRequest{
ErrorStack: errors.New(err).ErrorStack(),
Component: "gitpod-cli",
Version: gitpod.Version,
}
payload, err := json.Marshal(reportErrorRequest)
if err != nil {
log.WithError(err).Error("failed to marshal json while attempting to report error")
return
}
if err != nil {
return
}
cmd := exec.Command(
"/.supervisor/supervisor",
"error-report",
"--data",
string(payload),
)
cmd.Stdout = ioutil.Discard
cmd.Stderr = ioutil.Discard
err = cmd.Start()
if err != nil {
log.WithError(err).Error("cannot start error-report process")
return
}
if cmd.Process != nil {
_ = cmd.Process.Release()
}
}