diff --git a/cmd/minikube/cmd/delete.go b/cmd/minikube/cmd/delete.go index f4b0632313..4197d2d223 100644 --- a/cmd/minikube/cmd/delete.go +++ b/cmd/minikube/cmd/delete.go @@ -44,6 +44,7 @@ import ( "k8s.io/minikube/pkg/minikube/localpath" "k8s.io/minikube/pkg/minikube/machine" "k8s.io/minikube/pkg/minikube/out" + "k8s.io/minikube/pkg/minikube/out/register" ) var deleteAll bool @@ -125,6 +126,7 @@ func runDelete(cmd *cobra.Command, args []string) { if len(args) > 0 { exit.UsageT("Usage: minikube delete") } + register.SetEventLogPath(localpath.EventLog(ClusterFlagValue())) validProfiles, invalidProfiles, err := config.ListProfiles() if err != nil { diff --git a/cmd/minikube/cmd/pause.go b/cmd/minikube/cmd/pause.go index 448583d8fe..f73118f281 100644 --- a/cmd/minikube/cmd/pause.go +++ b/cmd/minikube/cmd/pause.go @@ -28,9 +28,11 @@ import ( "k8s.io/minikube/pkg/minikube/cruntime" "k8s.io/minikube/pkg/minikube/driver" "k8s.io/minikube/pkg/minikube/exit" + "k8s.io/minikube/pkg/minikube/localpath" "k8s.io/minikube/pkg/minikube/machine" "k8s.io/minikube/pkg/minikube/mustload" "k8s.io/minikube/pkg/minikube/out" + "k8s.io/minikube/pkg/minikube/out/register" ) var ( @@ -47,6 +49,7 @@ var pauseCmd = &cobra.Command{ func runPause(cmd *cobra.Command, args []string) { co := mustload.Running(ClusterFlagValue()) + register.SetEventLogPath(localpath.EventLog(ClusterFlagValue())) for _, n := range co.Config.Nodes { host, err := machine.LoadHost(co.API, driver.MachineName(*co.Config, n)) diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index 17dd1e73ca..75b9618e99 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -124,6 +124,8 @@ func platform() string { // runStart handles the executes the flow of "minikube start" func runStart(cmd *cobra.Command, args []string) { + register.SetEventLogPath(localpath.EventLog(ClusterFlagValue())) + out.SetJSON(viper.GetString(startOutput) == "json") displayVersion(version.GetVersion()) diff --git a/cmd/minikube/cmd/status.go b/cmd/minikube/cmd/status.go index c19983e1c1..96dd09dac3 100644 --- a/cmd/minikube/cmd/status.go +++ b/cmd/minikube/cmd/status.go @@ -17,6 +17,7 @@ limitations under the License. package cmd import ( + "bufio" "encoding/json" "fmt" "io" @@ -24,17 +25,21 @@ import ( "strings" "text/template" + cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/state" "github.com/golang/glog" "github.com/pkg/errors" "github.com/spf13/cobra" + "go.etcd.io/etcd/version" "k8s.io/minikube/pkg/minikube/bootstrapper/bsutil/kverify" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/driver" "k8s.io/minikube/pkg/minikube/exit" "k8s.io/minikube/pkg/minikube/kubeconfig" + "k8s.io/minikube/pkg/minikube/localpath" "k8s.io/minikube/pkg/minikube/machine" "k8s.io/minikube/pkg/minikube/mustload" "k8s.io/minikube/pkg/minikube/node" @@ -59,14 +64,34 @@ const ( Irrelevant = "Irrelevant" ) +type ExperimentalComponent struct { + Name string + Kind string + + Condition string + ConditionDetail []string + + Step string +} + +type ExperimentalCluster struct { + ExperimentalComponent + Components map[string]ExperimentalComponent +} + // Status holds string representations of component states type Status struct { + Version string + Name string Host string Kubelet string APIServer string Kubeconfig string Worker bool + + // Prototyping for the future + ExperimentalCluster ExperimentalCluster } const ( @@ -135,6 +160,8 @@ var statusCmd = &cobra.Command{ } } + addExperimentalFields(statuses[0]) + switch strings.ToLower(output) { case "text": for _, st := range statuses { @@ -154,6 +181,46 @@ var statusCmd = &cobra.Command{ }, } +func readEventLog(name string) ([]cloudevents.Event, error) { + path := localpath.EventLog(name) + + f, err := os.Open(path) + if err != nil { + return nil, errors.Wrap(err, "open") + } + var events []cloudevents.Event + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + var ev cloudevents.Event + if err = json.Unmarshal(scanner.Bytes(), &ev); err != nil { + return events, err + } + events = append(events, ev) + } + + return events, nil +} + +func addExperimentalFields(st *Status) { + st.ExperimentalCluster.Condition = st.APIServer + + evs, err := readEventLog(st.Name) + if err != nil { + glog.Errorf("unable to read event log: %v", err) + return + } + + for _, ev := range evs { + glog.Infof("read event: %+v", ev) + /* if ev.Type() == "io.k8s.sigs.minikube.step" { + st.ExperimentalCluster.Step = ev.Data.Name + } + */ + } + +} + func exitCode(statuses []*Status) int { c := 0 for _, st := range statuses { @@ -176,6 +243,7 @@ func status(api libmachine.API, cc config.ClusterConfig, n config.Node) (*Status name := driver.MachineName(cc, n) st := &Status{ + Version: version.Version, Name: name, Host: Nonexistent, APIServer: Nonexistent, diff --git a/cmd/minikube/cmd/stop.go b/cmd/minikube/cmd/stop.go index c2a1a2d0ce..e1dc331765 100644 --- a/cmd/minikube/cmd/stop.go +++ b/cmd/minikube/cmd/stop.go @@ -33,6 +33,7 @@ import ( "k8s.io/minikube/pkg/minikube/machine" "k8s.io/minikube/pkg/minikube/mustload" "k8s.io/minikube/pkg/minikube/out" + "k8s.io/minikube/pkg/minikube/out/register" "k8s.io/minikube/pkg/util/retry" ) @@ -60,6 +61,8 @@ func init() { // runStop handles the executes the flow of "minikube stop" func runStop(cmd *cobra.Command, args []string) { + register.SetEventLogPath(localpath.EventLog(ClusterFlagValue())) + // new code var profilesToStop []string if stopAll { diff --git a/cmd/minikube/cmd/unpause.go b/cmd/minikube/cmd/unpause.go index e7295b74cd..f19f646585 100644 --- a/cmd/minikube/cmd/unpause.go +++ b/cmd/minikube/cmd/unpause.go @@ -28,9 +28,11 @@ import ( "k8s.io/minikube/pkg/minikube/cruntime" "k8s.io/minikube/pkg/minikube/driver" "k8s.io/minikube/pkg/minikube/exit" + "k8s.io/minikube/pkg/minikube/localpath" "k8s.io/minikube/pkg/minikube/machine" "k8s.io/minikube/pkg/minikube/mustload" "k8s.io/minikube/pkg/minikube/out" + "k8s.io/minikube/pkg/minikube/out/register" ) // unpauseCmd represents the docker-pause command @@ -39,6 +41,8 @@ var unpauseCmd = &cobra.Command{ Short: "unpause Kubernetes", Run: func(cmd *cobra.Command, args []string) { cname := ClusterFlagValue() + register.SetEventLogPath(localpath.EventLog(cname)) + co := mustload.Running(cname) for _, n := range co.Config.Nodes { diff --git a/go.mod b/go.mod index bb925b15ae..4d6bc19e7c 100644 --- a/go.mod +++ b/go.mod @@ -70,6 +70,7 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f github.com/zchee/go-vmnet v0.0.0-20161021174912-97ebf9174097 + go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 golang.org/x/build v0.0.0-20190927031335-2835ba2e683f golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d @@ -83,6 +84,7 @@ require ( k8s.io/api v0.17.4 k8s.io/apimachinery v0.17.4 k8s.io/client-go v0.17.4 + k8s.io/klog v1.0.0 k8s.io/kubectl v0.0.0 k8s.io/kubernetes v1.17.3 k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab // indirect diff --git a/go.sum b/go.sum index e701fbf423..9266b3a808 100644 --- a/go.sum +++ b/go.sum @@ -244,6 +244,7 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -1030,6 +1031,7 @@ github.com/zchee/go-vmnet v0.0.0-20161021174912-97ebf9174097 h1:Ucx5I1l1+TWXvqFm github.com/zchee/go-vmnet v0.0.0-20161021174912-97ebf9174097/go.mod h1:lFZSWRIpCfE/pt91hHBBpV6+x87YlCjsp+aIR2qCPPU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= diff --git a/pkg/minikube/localpath/localpath.go b/pkg/minikube/localpath/localpath.go index b380354032..6ca674b4cf 100644 --- a/pkg/minikube/localpath/localpath.go +++ b/pkg/minikube/localpath/localpath.go @@ -61,6 +61,11 @@ func Profile(name string) string { return filepath.Join(MiniPath(), "profiles", name) } +// EventLog returns the path to a CloudEvents log +func EventLog(name string) string { + return filepath.Join(Profile(name), "events.json") +} + // ClientCert returns client certificate path, used by kubeconfig func ClientCert(name string) string { new := filepath.Join(Profile(name), "client.crt") diff --git a/pkg/minikube/out/out.go b/pkg/minikube/out/out.go index 908c29bc38..0af74575b7 100644 --- a/pkg/minikube/out/out.go +++ b/pkg/minikube/out/out.go @@ -78,6 +78,7 @@ func T(style StyleEnum, format string, a ...V) { register.PrintStep(outStyled) return } + register.RecordStep(outStyled) String(outStyled) } @@ -137,6 +138,8 @@ func Err(format string, a ...interface{}) { register.PrintError(format) return } + register.RecordError(format) + if errFile == nil { glog.Errorf("[unset errFile]: %s", fmt.Sprintf(format, a...)) return diff --git a/pkg/minikube/out/register/cloud_events.go b/pkg/minikube/out/register/cloud_events.go index 33dd089f7d..7213f40063 100644 --- a/pkg/minikube/out/register/cloud_events.go +++ b/pkg/minikube/out/register/cloud_events.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "os" + "path/filepath" cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/golang/glog" @@ -31,11 +32,33 @@ const ( ) var ( - OutputFile io.Writer = os.Stdout + outputFile io.Writer = os.Stdout GetUUID = randomID + + eventFile *os.File ) -func printAsCloudEvent(log Log, data map[string]string) { +func SetOutputFile(w io.Writer) { + outputFile = w +} + +func SetEventLogPath(path string) { + if _, err := os.Stat(filepath.Dir(path)); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { + glog.Errorf("Error creating profile directory", err) + return + } + } + + f, err := os.Create(path) + if err != nil { + glog.Errorf("unable to write to %s: %v", path, err) + return + } + eventFile = f +} + +func cloudEvent(log Log, data map[string]string) cloudevents.Event { event := cloudevents.NewEvent() event.SetSource("https://minikube.sigs.k8s.io/") event.SetType(log.Type()) @@ -44,11 +67,57 @@ func printAsCloudEvent(log Log, data map[string]string) { glog.Warningf("error setting data: %v", err) } event.SetID(GetUUID()) - json, err := event.MarshalJSON() + return event +} + +func printAsCloudEvent(log Log, data map[string]string) { + event := cloudEvent(log, data) + + bs, err := event.MarshalJSON() if err != nil { - glog.Warningf("error marashalling event: %v", err) + glog.Errorf("error marashalling event: %v", err) + return } - fmt.Fprintln(OutputFile, string(json)) + fmt.Fprintln(outputFile, string(bs)) +} + +func printAndRecordCloudEvent(log Log, data map[string]string) { + event := cloudEvent(log, data) + + bs, err := event.MarshalJSON() + if err != nil { + glog.Errorf("error marashalling event: %v", err) + return + } + fmt.Fprintln(outputFile, string(bs)) + + if eventFile != nil { + go storeEvent(bs) + } +} + +func storeEvent(bs []byte) { + glog.Errorf("writing to eventfile: %s", string(bs)) + fmt.Fprintln(eventFile, string(bs)) + if err := eventFile.Sync(); err != nil { + glog.Warningf("even file flush failed: %v", err) + } +} + +func recordCloudEvent(log Log, data map[string]string) { + if eventFile == nil { + return + } + + go func() { + event := cloudEvent(log, data) + bs, err := event.MarshalJSON() + if err != nil { + glog.Errorf("error marashalling event: %v", err) + return + } + storeEvent(bs) + }() } func randomID() string { diff --git a/pkg/minikube/out/register/json.go b/pkg/minikube/out/register/json.go index 06fae5980d..14f9449666 100644 --- a/pkg/minikube/out/register/json.go +++ b/pkg/minikube/out/register/json.go @@ -19,7 +19,13 @@ package register // PrintStep prints a Step type in JSON format func PrintStep(message string) { s := NewStep(message) - printAsCloudEvent(s, s.data) + printAndRecordCloudEvent(s, s.data) +} + +// RecordStep records a Step type in JSON format +func RecordStep(message string) { + s := NewStep(message) + recordCloudEvent(s, s.data) } // PrintInfo prints an Info type in JSON format @@ -31,7 +37,7 @@ func PrintInfo(message string) { // PrintDownload prints a Download type in JSON format func PrintDownload(artifact string) { s := NewDownload(artifact) - printAsCloudEvent(s, s.data) + printAndRecordCloudEvent(s, s.data) } // PrintDownloadProgress prints a DownloadProgress type in JSON format @@ -43,17 +49,23 @@ func PrintDownloadProgress(artifact, progress string) { // PrintError prints an Error type in JSON format func PrintError(err string) { e := NewError(err) - printAsCloudEvent(e, e.data) + printAndRecordCloudEvent(e, e.data) +} + +// RecordError records a Record type in JSON format +func RecordError(err string) { + e := NewError(err) + recordCloudEvent(e, e.data) } // PrintErrorExitCode prints an error in JSON format and includes an exit code func PrintErrorExitCode(err string, exitcode int, additionalArgs ...map[string]string) { e := NewErrorExitCode(err, exitcode, additionalArgs...) - printAsCloudEvent(e, e.data) + printAndRecordCloudEvent(e, e.data) } // PrintWarning prints a Warning type in JSON format func PrintWarning(warning string) { w := NewWarning(warning) - printAsCloudEvent(w, w.data) + printAndRecordCloudEvent(w, w.data) } diff --git a/pkg/minikube/out/register/json_test.go b/pkg/minikube/out/register/json_test.go index 0d1fb3a208..cd004aa03e 100644 --- a/pkg/minikube/out/register/json_test.go +++ b/pkg/minikube/out/register/json_test.go @@ -29,8 +29,8 @@ func TestPrintStep(t *testing.T) { expected += "\n" buf := bytes.NewBuffer([]byte{}) - OutputFile = buf - defer func() { OutputFile = os.Stdout }() + SetOutputFile(buf) + defer func() { SetOutputFile(os.Stdout) }() GetUUID = func() string { return "random-id" @@ -49,8 +49,8 @@ func TestPrintInfo(t *testing.T) { expected += "\n" buf := bytes.NewBuffer([]byte{}) - OutputFile = buf - defer func() { OutputFile = os.Stdout }() + SetOutputFile(buf) + defer func() { SetOutputFile(os.Stdout) }() GetUUID = func() string { return "random-id" @@ -69,8 +69,8 @@ func TestError(t *testing.T) { expected += "\n" buf := bytes.NewBuffer([]byte{}) - OutputFile = buf - defer func() { OutputFile = os.Stdout }() + SetOutputFile(buf) + defer func() { SetOutputFile(os.Stdout) }() GetUUID = func() string { return "random-id" @@ -89,8 +89,8 @@ func TestErrorExitCode(t *testing.T) { expected += "\n" buf := bytes.NewBuffer([]byte{}) - OutputFile = buf - defer func() { OutputFile = os.Stdout }() + SetOutputFile(buf) + defer func() { SetOutputFile(os.Stdout) }() GetUUID = func() string { return "random-id" @@ -107,8 +107,8 @@ func TestWarning(t *testing.T) { expected += "\n" buf := bytes.NewBuffer([]byte{}) - OutputFile = buf - defer func() { OutputFile = os.Stdout }() + SetOutputFile(buf) + defer func() { SetOutputFile(os.Stdout) }() GetUUID = func() string { return "random-id" diff --git a/pkg/minikube/out/register/register.go b/pkg/minikube/out/register/register.go index bdcbf86e7b..32a140735e 100644 --- a/pkg/minikube/out/register/register.go +++ b/pkg/minikube/out/register/register.go @@ -92,3 +92,5 @@ func (r *Register) currentStep() string { func (r *Register) SetStep(s RegStep) { r.current = s } + +// recordStep records the current step diff --git a/pkg/minikube/out/register/register_test.go b/pkg/minikube/out/register/register_test.go index e40c7a6678..049dd36214 100644 --- a/pkg/minikube/out/register/register_test.go +++ b/pkg/minikube/out/register/register_test.go @@ -32,8 +32,8 @@ func TestSetCurrentStep(t *testing.T) { expected += "\n" buf := bytes.NewBuffer([]byte{}) - OutputFile = buf - defer func() { OutputFile = os.Stdout }() + SetOutputFile(buf) + defer func() { SetOutputFile(os.Stdout) }() GetUUID = func() string { return "random-id" diff --git a/pkg/minikube/problem/problem_test.go b/pkg/minikube/problem/problem_test.go index c0134080dd..aa2ce233c9 100644 --- a/pkg/minikube/problem/problem_test.go +++ b/pkg/minikube/problem/problem_test.go @@ -121,8 +121,8 @@ func TestDisplayJSON(t *testing.T) { for _, tc := range tcs { t.Run(tc.p.ID, func(t *testing.T) { buf := bytes.NewBuffer([]byte{}) - register.OutputFile = buf - defer func() { register.OutputFile = os.Stdout }() + register.SetOutputFile(buf) + defer func() { register.SetOutputFile(os.Stdout) }() register.GetUUID = func() string { return "random-id"