diff --git a/cmd/metrics/parser.go b/cmd/metrics/parser.go new file mode 100644 index 0000000..019cfcf --- /dev/null +++ b/cmd/metrics/parser.go @@ -0,0 +1,93 @@ +package metrics + +import ( + "bufio" + "io" + "net/http" + "os" + "strings" + + cmdx "github.com/esonhugh/k8spider/cmd" + "github.com/esonhugh/k8spider/pkg/metrics" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var MetricOpt struct { + From string +} + +func init() { + cmdx.RootCmd.AddCommand(MetricCmd) + MetricCmd.PersistentFlags().StringVarP(&MetricOpt.From, "metric", "m", "", "metrics from (file / remote url)") + +} + +var MetricCmd = &cobra.Command{ + Use: "metric", + Short: "parse kube stat metrics to readable resource", + Run: func(cmd *cobra.Command, args []string) { + if MetricOpt.From == "" { + return + } + log.Debugf("parse metrics from %v", MetricOpt.From) + rule := metrics.DefaultMatchRules() + if err := rule.Compile(); err != nil { + log.Fatalf("compile rule failed: %v", err) + } + log.Debugf("compiled rules completed, start to get resource \n") + + ot := output() + + var r io.Reader + if strings.HasPrefix("http://", MetricOpt.From) || strings.HasPrefix("https://", MetricOpt.From) { + resp, err := http.Get(MetricOpt.From) + if err != nil { + log.Fatalf("get metrics from %v failed: %v", MetricOpt.From, err) + } + defer resp.Body.Close() + r = resp.Body + } else { + f, err := os.OpenFile(MetricOpt.From, os.O_RDONLY, 0666) + if err != nil { + log.Fatalf("open file %v failed: %v", MetricOpt.From, err) + } + defer f.Close() + r = f + } + log.Debugf("start to parse metrics line by line\n") + + var rx []*metrics.MetricMatcher + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + res, err := rule.Match(line) + if err != nil { + continue + } else { + log.Debugf("matched: %s", res.DumpString()) + rx = append(rx, res.CopyData()) + } + } + if err := scanner.Err(); err != nil { + log.Warnf("scan metrics failed and break out, reason: %v", err) + } + var res metrics.ResourceList = metrics.ConvertToResource(rx) + log.Debugf("parse metrics completed, start to print result\n") + + res.Print(ot) + }, +} + +func output() io.WriteCloser { + if cmdx.Opts.OutputFile != "" { + f, err := os.OpenFile(cmdx.Opts.OutputFile, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Warnf("create output file failed: %v", err) + return nil + } + return f + } else { + return os.Stdout + } +} diff --git a/main.go b/main.go index ca4739d..57547d5 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( _ "github.com/esonhugh/k8spider/cmd/all" _ "github.com/esonhugh/k8spider/cmd/axfr" _ "github.com/esonhugh/k8spider/cmd/dnsutils" + _ "github.com/esonhugh/k8spider/cmd/metrics" _ "github.com/esonhugh/k8spider/cmd/neighbor" _ "github.com/esonhugh/k8spider/cmd/service" _ "github.com/esonhugh/k8spider/cmd/subnet" diff --git a/pkg/metrics/metrics_test.go b/pkg/metrics/metrics_test.go index f248c46..245b6fb 100644 --- a/pkg/metrics/metrics_test.go +++ b/pkg/metrics/metrics_test.go @@ -60,6 +60,5 @@ func TestConvertToResource(t *testing.T) { rules = append(rules, r) } var res ResourceList = ConvertToResource(rules) - _, _ = output.WriteString(res.JSON() + "\n") - t.Logf(res.JSON()) + res.Print(os.Stderr) } diff --git a/pkg/metrics/resource.go b/pkg/metrics/resource.go index 79ebeae..8ad0982 100644 --- a/pkg/metrics/resource.go +++ b/pkg/metrics/resource.go @@ -1,6 +1,13 @@ package metrics -import "encoding/json" +import ( + "encoding/json" + "fmt" + "io" + "os" + + log "github.com/sirupsen/logrus" +) type Resource struct { Namespace string `json:"namespace"` @@ -37,12 +44,22 @@ func (r *Resource) JSON() string { return string(b) } -func (rl *ResourceList) JSON() string { - var res = "" +func (rl *ResourceList) Print(writer ...io.Writer) { + var W io.Writer + if len(writer) == 0 { + W = os.Stdout + } else { + w := io.MultiWriter(writer...) + W = io.MultiWriter(os.Stdout, w) + } for _, r := range *rl { - res += r.JSON() + "\n" + data, err := json.Marshal(r.JSON()) + if err != nil { + log.Error(err) + return + } + _, _ = fmt.Fprintf(W, "%v\n", string(data)) } - return res } type ResourceMergeHook func(m *MetricMatcher, resource ResourceList) (res *Resource, addFlag bool)