diff --git a/cmd/minikube/cmd/config/config.go b/cmd/minikube/cmd/config/config.go index 5b20a6e070..870d4b7f64 100644 --- a/cmd/minikube/cmd/config/config.go +++ b/cmd/minikube/cmd/config/config.go @@ -25,6 +25,7 @@ import ( "os" "github.com/spf13/cobra" + "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/constants" ) @@ -92,6 +93,18 @@ var settings = []Setting{ name: "kubernetes-version", set: SetString, }, + { + name: config.WantUpdateNotification, + set: SetBool, + }, + { + name: config.ReminderWaitPeriodInHours, + set: SetInt, + }, + { + name: config.WantReportError, + set: SetBool, + }, } var ConfigCmd = &cobra.Command{ diff --git a/cmd/minikube/cmd/config/set.go b/cmd/minikube/cmd/config/set.go index 2c1ed96c71..3e489e59b1 100644 --- a/cmd/minikube/cmd/config/set.go +++ b/cmd/minikube/cmd/config/set.go @@ -33,7 +33,7 @@ var configSetCmd = &cobra.Command{ fmt.Fprintln(os.Stderr, "usage: minikube config set PROPERTY_NAME PROPERTY_VALUE") os.Exit(1) } - err := set(args[0], args[1]) + err := Set(args[0], args[1]) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -45,7 +45,7 @@ func init() { ConfigCmd.AddCommand(configSetCmd) } -func set(name string, value string) error { +func Set(name string, value string) error { s, err := findSetting(name) if err != nil { return err diff --git a/cmd/minikube/cmd/config/set_test.go b/cmd/minikube/cmd/config/set_test.go index a2c35ecc59..b85057e27a 100644 --- a/cmd/minikube/cmd/config/set_test.go +++ b/cmd/minikube/cmd/config/set_test.go @@ -19,7 +19,7 @@ package config import "testing" func TestNotFound(t *testing.T) { - err := set("nonexistant", "10") + err := Set("nonexistant", "10") if err == nil { t.Fatalf("Set did not return error for unknown property") } diff --git a/cmd/minikube/cmd/dashboard.go b/cmd/minikube/cmd/dashboard.go index aa2181df73..e4f735e334 100644 --- a/cmd/minikube/cmd/dashboard.go +++ b/cmd/minikube/cmd/dashboard.go @@ -24,9 +24,9 @@ import ( "github.com/docker/machine/libmachine" "github.com/pkg/browser" "github.com/spf13/cobra" + cmdUtil "k8s.io/minikube/cmd/util" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/constants" - "k8s.io/minikube/pkg/util" commonutil "k8s.io/minikube/pkg/util" ) @@ -50,14 +50,14 @@ var dashboardCmd = &cobra.Command{ if err := commonutil.RetryAfter(20, func() error { return CheckService(namespace, service) }, 6*time.Second); err != nil { fmt.Fprintln(os.Stderr, "Could not find finalized endpoint being pointed to by %s: %s", service, err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } url, err := cluster.GetServiceURL(api, namespace, service) if err != nil { fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, "Check that minikube is running.") - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } if dashboardURLMode { fmt.Fprintln(os.Stdout, url) diff --git a/cmd/minikube/cmd/delete.go b/cmd/minikube/cmd/delete.go index 2f215cd583..757172e19d 100644 --- a/cmd/minikube/cmd/delete.go +++ b/cmd/minikube/cmd/delete.go @@ -21,9 +21,9 @@ import ( "github.com/docker/machine/libmachine" "github.com/spf13/cobra" + cmdUtil "k8s.io/minikube/cmd/util" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/constants" - "k8s.io/minikube/pkg/util" ) // deleteCmd represents the delete command @@ -39,7 +39,7 @@ associated files.`, if err := cluster.DeleteHost(api); err != nil { fmt.Println("Errors occurred deleting machine: ", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } fmt.Println("Machine deleted.") }, diff --git a/cmd/minikube/cmd/env.go b/cmd/minikube/cmd/env.go index 298aefe08f..124726f879 100644 --- a/cmd/minikube/cmd/env.go +++ b/cmd/minikube/cmd/env.go @@ -30,9 +30,9 @@ import ( "github.com/golang/glog" "github.com/pkg/errors" "github.com/spf13/cobra" + cmdUtil "k8s.io/minikube/cmd/util" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/constants" - "k8s.io/minikube/pkg/util" ) const ( @@ -241,13 +241,13 @@ var dockerEnvCmd = &cobra.Command{ shellCfg, err = shellCfgUnset(api) if err != nil { glog.Errorln("Error setting machine env variable(s):", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } } else { shellCfg, err = shellCfgSet(api) if err != nil { glog.Errorln("Error setting machine env variable(s):", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } } diff --git a/cmd/minikube/cmd/ip.go b/cmd/minikube/cmd/ip.go index 3fdd535823..f87e488c39 100644 --- a/cmd/minikube/cmd/ip.go +++ b/cmd/minikube/cmd/ip.go @@ -22,8 +22,8 @@ import ( "github.com/docker/machine/libmachine" "github.com/golang/glog" "github.com/spf13/cobra" + cmdUtil "k8s.io/minikube/cmd/util" "k8s.io/minikube/pkg/minikube/constants" - "k8s.io/minikube/pkg/util" ) // ipCmd represents the ip command @@ -37,12 +37,12 @@ var ipCmd = &cobra.Command{ host, err := api.Load(constants.MachineName) if err != nil { glog.Errorln("Error getting IP: ", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } ip, err := host.Driver.GetIP() if err != nil { glog.Errorln("Error getting IP: ", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } fmt.Println(ip) }, diff --git a/cmd/minikube/cmd/logs.go b/cmd/minikube/cmd/logs.go index 45b75752dc..0669561013 100644 --- a/cmd/minikube/cmd/logs.go +++ b/cmd/minikube/cmd/logs.go @@ -23,9 +23,9 @@ import ( "github.com/docker/machine/libmachine" "github.com/spf13/cobra" + cmdUtil "k8s.io/minikube/cmd/util" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/constants" - "k8s.io/minikube/pkg/util" ) // logsCmd represents the logs command @@ -39,7 +39,7 @@ var logsCmd = &cobra.Command{ s, err := cluster.GetHostLogs(api) if err != nil { log.Println("Error getting machine logs:", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } fmt.Fprintln(os.Stdout, s) }, diff --git a/cmd/minikube/cmd/root.go b/cmd/minikube/cmd/root.go index d72432e1e9..9aa62af7af 100644 --- a/cmd/minikube/cmd/root.go +++ b/cmd/minikube/cmd/root.go @@ -139,5 +139,6 @@ func setupViper() { viper.SetDefault(config.WantUpdateNotification, true) viper.SetDefault(config.ReminderWaitPeriodInHours, 24) viper.SetDefault(config.WantReportError, false) + viper.SetDefault(config.WantReportErrorPrompt, true) setFlagsUsingViper() } diff --git a/cmd/minikube/cmd/service.go b/cmd/minikube/cmd/service.go index 1e7883d768..71401c6fb6 100644 --- a/cmd/minikube/cmd/service.go +++ b/cmd/minikube/cmd/service.go @@ -27,6 +27,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" kubeApi "k8s.io/kubernetes/pkg/api" + cmdUtil "k8s.io/minikube/cmd/util" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/util" @@ -47,7 +48,7 @@ var serviceCmd = &cobra.Command{ if len(args) == 0 || len(args) > 1 { errText := "Please specify a service name." fmt.Fprintln(os.Stderr, errText) - util.MaybeReportErrorAndExit(errors.New(errText)) + cmdUtil.MaybeReportErrorAndExit(errors.New(errText)) } service := args[0] @@ -57,14 +58,14 @@ var serviceCmd = &cobra.Command{ cluster.EnsureMinikubeRunningOrExit(api) if err := util.RetryAfter(20, func() error { return CheckService(namespace, service) }, 6*time.Second); err != nil { fmt.Fprintln(os.Stderr, "Could not find finalized endpoint being pointed to by %s: %s", service, err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } url, err := cluster.GetServiceURL(api, namespace, service) if err != nil { fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, "Check that minikube is running and that you have specified the correct namespace (-n flag).") - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } if https { url = strings.Replace(url, "http", "https", 1) diff --git a/cmd/minikube/cmd/ssh.go b/cmd/minikube/cmd/ssh.go index 28af1b877e..066717b877 100644 --- a/cmd/minikube/cmd/ssh.go +++ b/cmd/minikube/cmd/ssh.go @@ -19,10 +19,11 @@ package cmd import ( "github.com/docker/machine/libmachine" "github.com/golang/glog" + "github.com/pkg/errors" "github.com/spf13/cobra" + cmdUtil "k8s.io/minikube/cmd/util" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/constants" - "k8s.io/minikube/pkg/util" ) // sshCmd represents the docker-ssh command @@ -34,9 +35,10 @@ var sshCmd = &cobra.Command{ api := libmachine.NewClient(constants.Minipath, constants.MakeMiniPath("certs")) defer api.Close() err := cluster.CreateSSHShell(api, args) + err = errors.Wrap(err, "Error attempting to ssh/run-ssh-command") if err != nil { - glog.Errorln("Error attempting to ssh into machine: ", err) - util.MaybeReportErrorAndExit(err) + glog.Errorln(err) + cmdUtil.MaybeReportErrorAndExit(err) } }, } diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index 59fef735a3..fd2cc90010 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -28,6 +28,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" cfg "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" + cmdUtil "k8s.io/minikube/cmd/util" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/minikube/kubeconfig" @@ -90,13 +91,13 @@ func runStart(cmd *cobra.Command, args []string) { err := util.Retry(3, start) if err != nil { glog.Errorln("Error starting host: ", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } ip, err := host.Driver.GetIP() if err != nil { glog.Errorln("Error starting host: ", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } kubernetesConfig := cluster.KubernetesConfig{ KubernetesVersion: viper.GetString(kubernetesVersion), @@ -107,17 +108,17 @@ func runStart(cmd *cobra.Command, args []string) { } if err := cluster.UpdateCluster(host, host.Driver, kubernetesConfig); err != nil { glog.Errorln("Error updating cluster: ", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } if err := cluster.SetupCerts(host.Driver); err != nil { glog.Errorln("Error configuring authentication: ", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } if err := cluster.StartCluster(host, kubernetesConfig); err != nil { glog.Errorln("Error starting cluster: ", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } kubeHost, err := host.Driver.GetURL() @@ -134,7 +135,7 @@ func runStart(cmd *cobra.Command, args []string) { clientKey := constants.MakeMiniPath("apiserver.key") if err := setupKubeconfig(name, kubeHost, certAuth, clientCert, clientKey); err != nil { glog.Errorln("Error setting up kubeconfig: ", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } fmt.Println("Kubectl is now configured to use the cluster.") } diff --git a/cmd/minikube/cmd/status.go b/cmd/minikube/cmd/status.go index 667480ea01..112e0770c5 100644 --- a/cmd/minikube/cmd/status.go +++ b/cmd/minikube/cmd/status.go @@ -24,9 +24,9 @@ import ( "github.com/docker/machine/libmachine/state" "github.com/golang/glog" "github.com/spf13/cobra" + cmdUtil "k8s.io/minikube/cmd/util" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/constants" - "k8s.io/minikube/pkg/util" ) var statusFormat string @@ -47,7 +47,7 @@ var statusCmd = &cobra.Command{ ms, err := cluster.GetHostStatus(api) if err != nil { glog.Errorln("Error getting machine status:", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } ls := "N/A" if ms == state.Running.String() { @@ -55,19 +55,19 @@ var statusCmd = &cobra.Command{ } if err != nil { glog.Errorln("Error getting machine status:", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } status := Status{ms, ls} tmpl, err := template.New("status").Parse(statusFormat) if err != nil { glog.Errorln("Error creating status template:", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } err = tmpl.Execute(os.Stdout, status) if err != nil { glog.Errorln("Error executing status template:", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } }, } diff --git a/cmd/minikube/cmd/stop.go b/cmd/minikube/cmd/stop.go index c67bcb0da8..23db86f001 100644 --- a/cmd/minikube/cmd/stop.go +++ b/cmd/minikube/cmd/stop.go @@ -21,9 +21,9 @@ import ( "github.com/docker/machine/libmachine" "github.com/spf13/cobra" + cmdUtil "k8s.io/minikube/cmd/util" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/constants" - "k8s.io/minikube/pkg/util" ) // stopCmd represents the stop command @@ -39,7 +39,7 @@ itself, leaving all files intact. The cluster can be started again with the "sta if err := cluster.StopHost(api); err != nil { fmt.Println("Error stopping machine: ", err) - util.MaybeReportErrorAndExit(err) + cmdUtil.MaybeReportErrorAndExit(err) } fmt.Println("Machine stopped.") }, diff --git a/cmd/util/util.go b/cmd/util/util.go new file mode 100644 index 0000000000..d8bfb04c21 --- /dev/null +++ b/cmd/util/util.go @@ -0,0 +1,164 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 util + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" + + "github.com/golang/glog" + "github.com/pkg/errors" + "github.com/spf13/viper" + "golang.org/x/crypto/ssh/terminal" + minikubeConfig "k8s.io/minikube/cmd/minikube/cmd/config" + "k8s.io/minikube/pkg/minikube/config" + "k8s.io/minikube/pkg/minikube/constants" + "k8s.io/minikube/pkg/version" +) + +type ServiceContext struct { + Service string `json:"service"` + Version string `json:"version"` +} + +type Message struct { + Message string `json:"message"` + ServiceContext `json:"serviceContext"` +} + +func ReportError(err error, url string) error { + errMsg, err := FormatError(err) + if err != nil { + return errors.Wrap(err, "Error formatting error message") + } + jsonErrorMsg, err := MarshallError(errMsg, "default", version.GetVersion()) + if err != nil { + return errors.Wrap(err, "Error marshalling error message to JSON") + } + err = UploadError(jsonErrorMsg, url) + if err != nil { + return errors.Wrap(err, "Error uploading error message") + } + return nil +} + +func FormatError(err error) (string, error) { + if err == nil { + return "", errors.New("Error: ReportError was called with nil error value") + } + + type stackTracer interface { + StackTrace() errors.StackTrace + } + + errOutput := []string{} + errOutput = append(errOutput, err.Error()) + + if err, ok := err.(stackTracer); ok { + for _, f := range err.StackTrace() { + errOutput = append(errOutput, fmt.Sprintf("\tat %n(%v)", f, f)) + } + } else { + return "", errors.New("Error msg with no stack trace cannot be reported") + } + return strings.Join(errOutput, "\n"), nil +} + +func MarshallError(errMsg, service, version string) ([]byte, error) { + m := Message{errMsg, ServiceContext{service, version}} + b, err := json.Marshal(m) + if err != nil { + return nil, errors.Wrap(err, "") + } + return b, nil +} + +func UploadError(b []byte, url string) error { + req, err := http.NewRequest("POST", url, bytes.NewBuffer(b)) + if err != nil { + return errors.Wrap(err, "") + } + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return errors.Wrap(err, "") + } else if resp.StatusCode != 200 { + return errors.Errorf("Error sending error report to %s, got response code %s", url, resp.StatusCode) + } + return nil +} + +func MaybeReportErrorAndExit(errToReport error) { + var err error + if viper.GetBool(config.WantReportError) { + err = ReportError(errToReport, constants.ReportingURL) + } else if viper.GetBool(config.WantReportErrorPrompt) { + fmt.Println("========================================" + + "An error has occurred. Would you like to opt in to sending anonymized crash information to minikube to help prevent future errors?" + + "(To opt out of these messages, run the command)\n\tminikube config set WantReportErrorPrompt false" + + "========================================") + if PromptUserForAccept(os.Stdin) { + minikubeConfig.Set(config.WantReportError, "true") + err = ReportError(errToReport, constants.ReportingURL) + } + } + if err != nil { + glog.Errorf(err.Error()) + } + os.Exit(1) +} + +func getInput(input chan string, r io.Reader) { + reader := bufio.NewReader(r) + fmt.Print("Please enter your response [Y/n]: \n") + response, err := reader.ReadString('\n') + if err != nil { + glog.Errorf(err.Error()) + } + input <- response +} + +func PromptUserForAccept(r io.Reader) bool { + if !terminal.IsTerminal(int(os.Stdout.Fd())) { + return false + } + input := make(chan string, 1) + go getInput(input, r) + select { + case response := <-input: + response = strings.ToLower(strings.TrimSpace(response)) + if response == "y" || response == "yes" || response == "" { + return true + } else if response == "n" || response == "no" { + return false + } else { + fmt.Println("Invalid response, error reporting remains disabled. Must be in form [Y/n]") + return false + } + case <-time.After(30 * time.Second): + return false + } +} diff --git a/cmd/util/util_test.go b/cmd/util/util_test.go new file mode 100644 index 0000000000..ff03611581 --- /dev/null +++ b/cmd/util/util_test.go @@ -0,0 +1,83 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 util + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "k8s.io/minikube/pkg/version" + + "github.com/pkg/errors" +) + +func TestFormatError(t *testing.T) { + var testErr error + if _, err := FormatError(testErr); err == nil { + t.Fatalf("FormatError should have errored with a nil error input") + } + testErr = fmt.Errorf("Not a valid error to format as there is no stacktrace") + + if out, err := FormatError(testErr); err == nil { + t.Fatalf("FormatError should have errored with a non pkg/errors error (no stacktrace info): %s", out) + } + + testErr = errors.New("TestFormatError 1") + errors.Wrap(testErr, "TestFormatError 2") + errors.Wrap(testErr, "TestFormatError 3") + + _, err := FormatError(testErr) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } +} + +func TestMarshallError(t *testing.T) { + testErr := errors.New("TestMarshallError 1") + errors.Wrap(testErr, "TestMarshallError 2") + errors.Wrap(testErr, "TestMarshallError 3") + + errMsg, _ := FormatError(testErr) + if _, err := MarshallError(errMsg, "default", version.GetVersion()); err != nil { + t.Fatalf("Unexpected error: %s", err) + } +} + +func TestUploadError(t *testing.T) { + testErr := errors.New("TestUploadError 1") + errors.Wrap(testErr, "TestUploadError 2") + errors.Wrap(testErr, "TestUploadError 3") + errMsg, _ := FormatError(testErr) + jsonErrMsg, _ := MarshallError(errMsg, "default", version.GetVersion()) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, world!") + })) + + if err := UploadError(jsonErrMsg, server.URL); err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "failed to write report", 400) + })) + if err := UploadError(jsonErrMsg, server.URL); err == nil { + t.Fatalf("UploadError should have errored from a 400 response") + } +} diff --git a/docs/minikube_config.md b/docs/minikube_config.md index 322492370a..1b73400337 100644 --- a/docs/minikube_config.md +++ b/docs/minikube_config.md @@ -17,6 +17,9 @@ Configurable fields: * show-libmachine-logs * log_dir * kubernetes-version + * WantUpdateNotification + * ReminderWaitPeriodInHours + * WantReportError ``` minikube config SUBCOMMAND [flags] diff --git a/pkg/minikube/config/config.go b/pkg/minikube/config/config.go index 298fe70b09..a000e6d194 100644 --- a/pkg/minikube/config/config.go +++ b/pkg/minikube/config/config.go @@ -20,4 +20,5 @@ const ( WantUpdateNotification = "WantUpdateNotification" ReminderWaitPeriodInHours = "ReminderWaitPeriodInHours" WantReportError = "WantReportError" + WantReportErrorPrompt = "WantReportErrorPrompt" ) diff --git a/pkg/minikube/constants/constants.go b/pkg/minikube/constants/constants.go index 87084ec9ac..40b6b404a4 100644 --- a/pkg/minikube/constants/constants.go +++ b/pkg/minikube/constants/constants.go @@ -85,3 +85,5 @@ var LocalkubeLinuxFilename = "localkube-linux-amd64" // DockerAPIVersion is the API version implemented by Docker running in the minikube VM. const DockerAPIVersion = "1.23" + +const ReportingURL = "https://clouderrorreporting.googleapis.com/v1beta1/projects/k8s-minikube/events:report?key=AIzaSyACUwzG0dEPcl-eOgpDKnyKoUFgHdfoFuA" diff --git a/pkg/util/constants.go b/pkg/util/constants.go index b51f37c3e6..7c0bc88132 100644 --- a/pkg/util/constants.go +++ b/pkg/util/constants.go @@ -28,5 +28,3 @@ const ( func GetAlternateDNS(domain string) []string { return []string{"kubernetes.default.svc." + domain, "kubernetes.default.svc", "kubernetes.default", "kubernetes"} } - -const reportingURL = "https://clouderrorreporting.googleapis.com/v1beta1/projects/k8s-minikube/events:report?key=AIzaSyACUwzG0dEPcl-eOgpDKnyKoUFgHdfoFuA" diff --git a/pkg/util/utils.go b/pkg/util/utils.go index b19de86c1a..d216fa2a1f 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -17,22 +17,17 @@ limitations under the License. package util import ( - "bytes" - "encoding/json" "fmt" "io" "io/ioutil" "net/http" "net/url" "os" - "path/filepath" "strings" "time" "github.com/blang/semver" "github.com/pkg/errors" - "github.com/spf13/viper" - "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/version" ) @@ -159,88 +154,3 @@ func IsDirectory(path string) (bool, error) { } return fileInfo.IsDir(), nil } - -type ServiceContext struct { - Service string `json:"service"` - Version string `json:"version"` -} - -type Message struct { - Message string `json:"message"` - ServiceContext `json:"serviceContext"` -} - -func ReportError(err error, url string) error { - errMsg, err := FormatError(err) - if err != nil { - return errors.Wrap(err, "") - } - jsonErrorMsg, err := MarshallError(errMsg, "default", version.GetVersion()) - if err != nil { - return errors.Wrap(err, "") - } - if err != nil { - return errors.Wrap(err, "") - } - err = UploadError(jsonErrorMsg, url) - if err != nil { - return errors.Wrap(err, "") - } - return nil -} - -func FormatError(err error) (string, error) { - if err == nil { - return "", errors.New("Error: ReportError was called with nil error value") - } - // Extract the stacktrace from the error messages in their orig format - errMsg := fmt.Sprintf("%+v\n", err) - - errArray := strings.Split(errMsg, "\n") - errOutput := []string{} - - //Error message must have at least 1 message w/ 1 stack trace(2 lines) -> 3 lines, 2 index - if len(errArray) <= 2 { - return "", errors.New("Error msg with no stack trace cannot be reported") - } - // This code is to format the error stacktraces so that StackDriver will accept them - errOutput = append(errOutput, errArray[0]) - for i := 1; i < len(errArray)-1; i += 2 { - errOutput = append(errOutput, fmt.Sprintf("\tat %s (%s)", errArray[i], - filepath.Base(errArray[i+1]))) - } - return strings.Join(errOutput, "\n") + "\n", nil -} - -func MarshallError(errMsg, service, version string) ([]byte, error) { - m := Message{errMsg, ServiceContext{service, version}} - b, err := json.Marshal(m) - if err != nil { - return nil, errors.Wrap(err, "") - } - return b, nil -} - -func UploadError(b []byte, url string) error { - req, err := http.NewRequest("POST", url, bytes.NewBuffer(b)) - if err != nil { - return errors.Wrap(err, "") - } - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return errors.Wrap(err, "") - } else if resp.StatusCode != 200 { - return errors.Errorf("Error sending error report to %s, got response code %s", url, resp.StatusCode) - } - return nil -} - -func MaybeReportErrorAndExit(err error) { - if viper.GetBool(config.WantReportError) { - ReportError(err, reportingURL) - } - os.Exit(1) -} diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go index 3b19e29d81..b4f50f0ec7 100644 --- a/pkg/util/utils_test.go +++ b/pkg/util/utils_test.go @@ -17,14 +17,10 @@ limitations under the License. package util import ( - "fmt" - "net/http" - "net/http/httptest" "testing" "github.com/pkg/errors" "k8s.io/minikube/pkg/minikube/constants" - "k8s.io/minikube/pkg/version" ) // Returns a function that will return n errors, then return successfully forever. @@ -112,58 +108,3 @@ Error 2` t.Fatalf("Unexpected error: %s", err) } } - -func TestFormatError(t *testing.T) { - var testErr error - if _, err := FormatError(testErr); err == nil { - t.Fatalf("FormatError should have errored with a nil error input") - } - testErr = fmt.Errorf("Not a valid error to format as there is no stacktrace") - - if out, err := FormatError(testErr); err == nil { - t.Fatalf("FormatError should have errored with a non pkg/errors error (no stacktrace info): %s", out) - } - - testErr = errors.New("TestFormatError 1") - errors.Wrap(testErr, "TestFormatError 2") - errors.Wrap(testErr, "TestFormatError 3") - - _, err := FormatError(testErr) - if err != nil { - t.Fatalf("Unexpected error: %s", err) - } -} - -func TestMarshallError(t *testing.T) { - testErr := errors.New("TestMarshallError 1") - errors.Wrap(testErr, "TestMarshallError 2") - errors.Wrap(testErr, "TestMarshallError 3") - - errMsg, _ := FormatError(testErr) - if _, err := MarshallError(errMsg, "default", version.GetVersion()); err != nil { - t.Fatalf("Unexpected error: %s", err) - } -} - -func TestUploadError(t *testing.T) { - testErr := errors.New("TestUploadError 1") - errors.Wrap(testErr, "TestUploadError 2") - errors.Wrap(testErr, "TestUploadError 3") - errMsg, _ := FormatError(testErr) - jsonErrMsg, _ := MarshallError(errMsg, "default", version.GetVersion()) - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, world!") - })) - - if err := UploadError(jsonErrMsg, server.URL); err != nil { - t.Fatalf("Unexpected error: %s", err) - } - - server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "failed to write report", 400) - })) - if err := UploadError(jsonErrMsg, server.URL); err == nil { - t.Fatalf("UploadError should have errored from a 400 response") - } -}