From cc0acb220ad75e4c6256dc4fc6bb52507970461a Mon Sep 17 00:00:00 2001 From: pixiake Date: Wed, 25 Sep 2024 17:27:06 +0800 Subject: [PATCH] Support exporting the results to Excel Signed-off-by: pixiake --- build/apiserver/Dockerfile | 4 +- build/controller/Dockerfile | 4 +- build/job/Dockerfile | 4 +- go.mod | 16 +- go.sum | 33 ++- pkg/constant/constant.go | 2 + pkg/controllers/inspectplan_controller.go | 14 +- pkg/controllers/inspectresult_controller.go | 4 + pkg/controllers/inspecttask_controller.go | 37 ++- pkg/output/excel.go | 266 ++++++++++++++++++++ pkg/output/html.go | 145 +++++++++-- pkg/server/api/inspectResult.go | 17 ++ pkg/server/router/router.go | 1 + 13 files changed, 505 insertions(+), 42 deletions(-) create mode 100644 pkg/output/excel.go diff --git a/build/apiserver/Dockerfile b/build/apiserver/Dockerfile index b6a7e410..9102dfbd 100644 --- a/build/apiserver/Dockerfile +++ b/build/apiserver/Dockerfile @@ -1,6 +1,6 @@ # Build the manager binary -FROM --platform=${TARGETPLATFORM} golang:1.21 as builder +FROM golang:1.21 AS builder #RUN #go env -w GOPROXY=https://goproxy.cn,direct # Run this with docker build --build_arg $(go env GOPROXY) to override the goproxy @@ -30,7 +30,7 @@ RUN go mod tidy && go mod vendor RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETPLATFORM} go build -o /workspace/apiserver ./cmd/apiserver -FROM --platform=${TARGETPLATFORM} alpine:3.19 +FROM alpine:3.19 WORKDIR / diff --git a/build/controller/Dockerfile b/build/controller/Dockerfile index 06cc818b..16bbeb0d 100644 --- a/build/controller/Dockerfile +++ b/build/controller/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.21 as builder +FROM golang:1.21 AS builder #ARG goproxy=https://goproxy.cn,direct #ENV GOPROXY=$goproxy @@ -27,7 +27,7 @@ ENV CGO_ENABLED=0 # Build RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETPLATFORM} go build -o /workspace/controller ./cmd/ke-manager/main.go -FROM alpine:3.19 as ke-manager +FROM alpine:3.19 AS ke-manager WORKDIR /kubeeye diff --git a/build/job/Dockerfile b/build/job/Dockerfile index e10bdfa5..6e02a172 100644 --- a/build/job/Dockerfile +++ b/build/job/Dockerfile @@ -1,6 +1,6 @@ # Build the manager binary -FROM --platform=${TARGETPLATFORM} golang:1.21 as builder +FROM golang:1.21 AS builder #RUN #go env -w GOPROXY=https://goproxy.cn,direct # Run this with docker build --build_arg $(go env GOPROXY) to override the goproxy @@ -31,7 +31,7 @@ RUN go mod tidy && go mod vendor RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETPLATFORM} go build -o /workspace/ke ./cmd/ke -FROM --platform=${TARGETPLATFORM} alpine:3.19 +FROM alpine:3.19 WORKDIR / diff --git a/go.mod b/go.mod index d05881b8..1060ab3c 100644 --- a/go.mod +++ b/go.mod @@ -22,12 +22,13 @@ require ( github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.1 + github.com/xuri/excelize/v2 v2.8.1 go.uber.org/zap v1.22.0 golang.org/x/sys v0.20.0 - k8s.io/api v0.24.3 - k8s.io/apimachinery v0.24.3 - k8s.io/client-go v0.24.3 - k8s.io/code-generator v0.24.2 + k8s.io/api v0.24.16 + k8s.io/apimachinery v0.24.16 + k8s.io/client-go v0.24.16 + k8s.io/code-generator v0.24.16 k8s.io/klog/v2 v2.60.1 k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 sigs.k8s.io/controller-runtime v0.12.3 @@ -96,11 +97,14 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.3 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -115,6 +119,8 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect + github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect go.opentelemetry.io/otel v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect @@ -145,6 +151,6 @@ require ( k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 // indirect k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index dc5c36a5..5ad18e52 100644 --- a/go.sum +++ b/go.sum @@ -618,6 +618,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -702,6 +704,11 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= +github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -807,6 +814,12 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 h1:Chd9DkqERQQuHpXjR/HSV1jLZA6uaoiwwH3vSuF3IW0= +github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.8.1 h1:pZLMEwK8ep+CLIUWpWmvW8IWE/yxqG0I1xcN6cVMGuQ= +github.com/xuri/excelize/v2 v2.8.1/go.mod h1:oli1E4C3Pa5RXg1TBXn4ENCXDV5JUMlBluUhG7c+CEE= +github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4= +github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -930,6 +943,8 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjs golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= +golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1455,21 +1470,22 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8= k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= -k8s.io/api v0.24.3 h1:tt55QEmKd6L2k5DP6G/ZzdMQKvG5ro4H4teClqm0sTY= -k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI= +k8s.io/api v0.24.16 h1:9G8eHAtCvY8dLmTmRI/+O1/7alkcz29UKGyLkeMSRw8= +k8s.io/api v0.24.16/go.mod h1:lNKdTj0W2upnaS9S5kvHTU5T/NTHnDdmQjUjODC8JZs= k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k= k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ= k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/apimachinery v0.24.3 h1:hrFiNSA2cBZqllakVYyH/VyEh4B581bQRmqATJSeQTg= -k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apimachinery v0.24.16 h1:3u2XdCZcV0PUagOuH1+b0vVfZwnOhVwSauuVlIYH278= +k8s.io/apimachinery v0.24.16/go.mod h1:kSzhCwldu9XB172NDdLffRN0sJ3x95RR7Bmyc4SHhs0= k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI= k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4= k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= -k8s.io/client-go v0.24.3 h1:Nl1840+6p4JqkFWEW2LnMKU667BUxw03REfLAVhuKQY= -k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw= -k8s.io/code-generator v0.24.2 h1:EGeRWzJrpwi6T6CvoNl0spM6fnAnOdCr0rz7H4NU1rk= +k8s.io/client-go v0.24.16 h1:ZL3OsVQ2FVr4/yo+1ydzuZ/RTDwxtkPHY9kWuI+uF3Q= +k8s.io/client-go v0.24.16/go.mod h1:H1io/ZQK4Cju1fO5tg9njKjqPups9MIZlKGaUDq3Q64= k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= +k8s.io/code-generator v0.24.16 h1:2gmx5agslgReq/VlslmLwBW0K3bZI7e6SbRaiEWbLnc= +k8s.io/code-generator v0.24.16/go.mod h1:nQvp6VgOfRkKiLyMz+/JTNXNS6Q4bGWOVtB5rKd2TV0= k8s.io/component-base v0.24.2 h1:kwpQdoSfbcH+8MPN4tALtajLDfSfYxBDYlXobNWI6OU= k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -1499,8 +1515,9 @@ sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNza sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index 36f0250d..5fff4272 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -28,6 +28,8 @@ const ( Component = "component" CustomCommand = "customcommand" NodeInfo = "nodeinfo" + NodesStatus = "nodes status" + AbnormalPods = "abnormal pods" ) const ( diff --git a/pkg/controllers/inspectplan_controller.go b/pkg/controllers/inspectplan_controller.go index b8816d76..531bdacf 100644 --- a/pkg/controllers/inspectplan_controller.go +++ b/pkg/controllers/inspectplan_controller.go @@ -183,6 +183,7 @@ func (r *InspectPlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) } plan.Status.NextScheduleTime = &metav1.Time{Time: schedule.Next(now)} + r.Status().Patch(ctx, plan, client.MergeFrom(plan)) if err = r.updateStatus(ctx, plan, now, taskName); err != nil { return ctrl.Result{}, err } @@ -213,10 +214,19 @@ func nextScheduledTimeDuration(sched cron.Schedule, now *metav1.Time) *time.Dura } func (r *InspectPlanReconciler) createInspectTask(plan *kubeeyev1alpha2.InspectPlan, ctx context.Context) (string, error) { + + inspectTaskName := fmt.Sprintf("%s-%s", plan.Name, time.Now().Format("20060102-15-04")) + + err := r.Client.Get(ctx, client.ObjectKey{Name: inspectTaskName}, &kubeeyev1alpha2.InspectTask{}) + if err == nil { + klog.Info("InspectTask already exists. ", "InspectTask: ", inspectTaskName) + return inspectTaskName, nil + } + ownerController := true inspectTask := kubeeyev1alpha2.InspectTask{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s", plan.Name, time.Now().Format("20060102-15-04")), + Name: inspectTaskName, Labels: map[string]string{constant.LabelPlanName: plan.Name}, Annotations: map[string]string{constant.AnnotationInspectType: func() string { if plan.Spec.Schedule == nil { @@ -251,7 +261,7 @@ func (r *InspectPlanReconciler) createInspectTask(plan *kubeeyev1alpha2.InspectP }, } - err := r.Client.Create(ctx, &inspectTask) + err = r.Client.Create(ctx, &inspectTask) if err != nil { return "", err } diff --git a/pkg/controllers/inspectresult_controller.go b/pkg/controllers/inspectresult_controller.go index 9a00d623..852df9df 100644 --- a/pkg/controllers/inspectresult_controller.go +++ b/pkg/controllers/inspectresult_controller.go @@ -93,6 +93,10 @@ func (r *InspectResultReconciler) Reconcile(ctx context.Context, req ctrl.Reques if err != nil { klog.Error(err, "failed to delete file") } + err = os.Remove(fmt.Sprintf("%s.xlsx", path.Join(constant.ResultPathPrefix, result.Name))) + if err != nil { + klog.Error(err, "failed to delete xlsx file") + } err = r.Client.Update(ctx, result) if err != nil { klog.Error("Failed to inspect plan add finalizers. ", err) diff --git a/pkg/controllers/inspecttask_controller.go b/pkg/controllers/inspecttask_controller.go index d5e72c7a..c1d9724a 100644 --- a/pkg/controllers/inspecttask_controller.go +++ b/pkg/controllers/inspecttask_controller.go @@ -22,6 +22,7 @@ import ( "fmt" kubeeyeInformers "github.com/kubesphere/kubeeye/clients/informers/externalversions/kubeeye" "github.com/kubesphere/kubeeye/pkg/constant" + "github.com/kubesphere/kubeeye/pkg/output" "github.com/kubesphere/kubeeye/pkg/rules" "github.com/kubesphere/kubeeye/pkg/template" "github.com/kubesphere/kubeeye/pkg/utils" @@ -258,9 +259,21 @@ func (r *InspectTaskReconciler) GenerateResult(task *kubeeyev1alpha2.InspectTask } } + file, err = os.Open(fmt.Sprintf("%s.xlsx", path.Join(constant.ResultPathPrefix, resultName))) + if err == nil { + defer file.Close() + err = os.Remove(fmt.Sprintf("%s.xlsx", path.Join(constant.ResultPathPrefix, resultName))) + if err != nil { + klog.Error("failed to delete result xlsx file") + } + } + return kubeeyev1alpha2.InspectResult{ ObjectMeta: metav1.ObjectMeta{Name: resultName, - Labels: map[string]string{constant.LabelTaskName: task.Name}, + Labels: map[string]string{ + constant.LabelTaskName: task.Name, + constant.LabelPlanName: task.Labels[constant.LabelPlanName], + }, Annotations: map[string]string{ constant.AnnotationStartTime: task.Status.StartTimestamp.Format("2006-01-02 15:04:05"), constant.AnnotationEndTime: task.Status.EndTimestamp.Format("2006-01-02 15:04:05"), @@ -440,7 +453,19 @@ func (r *InspectTaskReconciler) getInspectResultData(ctx context.Context, client } } - err = saveResultFile(resultData) + // get all nodes + nodes, err := clients.ClientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + + // Get all pods + pods, err := clients.ClientSet.CoreV1().Pods("").List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + + err = saveResultFile(resultData, nodes, pods) if err != nil { return err } @@ -453,7 +478,8 @@ func (r *InspectTaskReconciler) getInspectResultData(ctx context.Context, client return nil } -func saveResultFile(resultData *kubeeyev1alpha2.InspectResult) error { +func saveResultFile(resultData *kubeeyev1alpha2.InspectResult, nodes *corev1.NodeList, pods *corev1.PodList) error { + // json file, err := os.OpenFile(path.Join(constant.ResultPathPrefix, resultData.Name), os.O_CREATE|os.O_WRONLY, 0775) if err != nil { klog.Error(err, "open file error") @@ -469,6 +495,11 @@ func saveResultFile(resultData *kubeeyev1alpha2.InspectResult) error { klog.Error(err, "write file error") return err } + // execl + err = output.GenerateExcel(resultData, nodes, pods) + if err != nil { + klog.Error(err, "generate excel error") + } return nil } diff --git a/pkg/output/excel.go b/pkg/output/excel.go new file mode 100644 index 00000000..70a1ce9e --- /dev/null +++ b/pkg/output/excel.go @@ -0,0 +1,266 @@ +package output + +import ( + "fmt" + kubeeyev1alpha2 "github.com/kubesphere/kubeeye/apis/kubeeye/v1alpha2" + "github.com/kubesphere/kubeeye/pkg/constant" + "github.com/xuri/excelize/v2" + corev1 "k8s.io/api/core/v1" + "path" +) + +func GenerateExcel(resultData *kubeeyev1alpha2.InspectResult, nodes *corev1.NodeList, pods *corev1.PodList) error { + f := excelize.NewFile() + defer func() error { + if err := f.Close(); err != nil { + return err + } + return nil + }() + // create a new sheet for Nodes + if len(nodes.Items) > 0 { + index, err := f.NewSheet(constant.NodesStatus) + if err != nil { + return err + } + + nodeStatus := GetNodesStatus(nodes) + + // Write the data to the sheet + for i, item := range nodeStatus { + if i == 0 { + for j, c := range item.Children { + f.SetCellValue(constant.NodesStatus, fmt.Sprintf("%c1", 'A'+rune(j)), c.Text) + } + } else { + for j, c := range item.Children { + f.SetCellValue(constant.NodesStatus, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + f.SetActiveSheet(index) + } + + // Create a new sheet for Pods + if len(pods.Items) > 0 { + _, err := f.NewSheet(constant.AbnormalPods) + if err != nil { + return err + } + + podStatus := GetAbnormalPods(pods) + + // Write the data to the sheet + for i, item := range podStatus { + if i == 0 { + for j, c := range item.Children { + f.SetCellValue(constant.AbnormalPods, fmt.Sprintf("%c1", 'A'+rune(j)), c.Text) + } + } else { + for j, c := range item.Children { + f.SetCellValue(constant.AbnormalPods, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + } + + // Create a new sheet for OPA + if resultData.Spec.OpaResult.ResourceResults != nil { + _, err := f.NewSheet(constant.Opa) + if err != nil { + return err + } + + opaResults := GetOpaList(resultData.Spec.OpaResult.ResourceResults) + + // Write the data to the sheet + for i, item := range opaResults { + if i == 0 { + for j, c := range item.Children { + f.SetCellValue(constant.Opa, fmt.Sprintf("%c1", 'A'+rune(j)), c.Text) + } + } else { + for j, c := range item.Children { + f.SetCellValue(constant.Opa, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + } + + // Create a new sheet for systemd + if resultData.Spec.SystemdResult != nil { + _, err := f.NewSheet(constant.Systemd) + if err != nil { + return err + } + + systemdResults := GetSystemd(resultData.Spec.SystemdResult) + + // Write the data to the sheet + for i, item := range systemdResults { + if i == 0 { + for j, c := range item.Children { + f.SetCellValue(constant.Systemd, fmt.Sprintf("%c1", 'A'+rune(j)), c.Text) + } + } else { + for j, c := range item.Children { + f.SetCellValue(constant.Systemd, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + } + + // Create a new sheet for prometheus + if resultData.Spec.PrometheusResult != nil { + _, err := f.NewSheet(constant.Prometheus) + if err != nil { + return err + } + + prometheusReults := GetPrometheus(resultData.Spec.PrometheusResult) + // Write the data to the sheet + for i, item := range prometheusReults { + for j, c := range item.Children { + f.SetCellValue(constant.Prometheus, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + + // Create a new sheet for fileChange + if resultData.Spec.FileChangeResult != nil { + _, err := f.NewSheet(constant.FileChange) + if err != nil { + return err + } + + fileChangeResults := GetFileChange(resultData.Spec.FileChangeResult) + // Write the data to the sheet + for i, item := range fileChangeResults { + if i == 0 { + for j, c := range item.Children { + f.SetCellValue(constant.FileChange, fmt.Sprintf("%c1", 'A'+rune(j)), c.Text) + } + } else { + for j, c := range item.Children { + f.SetCellValue(constant.FileChange, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + } + + // Create a new sheet for sysctl + if resultData.Spec.SysctlResult != nil { + _, err := f.NewSheet(constant.Sysctl) + if err != nil { + return err + } + + sysctlResults := GetSysctl(resultData.Spec.SysctlResult) + // Write the data to the sheet + for i, item := range sysctlResults { + if i == 0 { + for j, c := range item.Children { + f.SetCellValue(constant.Sysctl, fmt.Sprintf("%c1", 'A'+rune(j)), c.Text) + } + } else { + for j, c := range item.Children { + f.SetCellValue(constant.Sysctl, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + } + + // Create a new sheet for filefilter + if resultData.Spec.FileFilterResult != nil { + _, err := f.NewSheet(constant.FileFilter) + if err != nil { + return err + } + + fileFilterResults := GetFileFilter(resultData.Spec.FileFilterResult) + // Write the data to the sheet + for i, item := range fileFilterResults { + if i == 0 { + for j, c := range item.Children { + f.SetCellValue(constant.FileFilter, fmt.Sprintf("%c1", 'A'+rune(j)), c.Text) + } + } else { + for j, c := range item.Children { + f.SetCellValue(constant.FileFilter, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + } + + // Create a new sheet for command + if resultData.Spec.CommandResult != nil { + _, err := f.NewSheet(constant.CustomCommand) + if err != nil { + return err + } + + commandResults := GetCommand(resultData.Spec.CommandResult) + // Write the data to the sheet + for i, item := range commandResults { + if i == 0 { + for j, c := range item.Children { + f.SetCellValue(constant.CustomCommand, fmt.Sprintf("%c1", 'A'+rune(j)), c.Text) + } + } else { + for j, c := range item.Children { + f.SetCellValue(constant.CustomCommand, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + } + + // Create a new sheet for nodeinfo + if resultData.Spec.NodeInfo != nil { + _, err := f.NewSheet(constant.NodeInfo) + if err != nil { + return err + } + + nodeInfoResults := GetNodeInfo(resultData.Spec.NodeInfo) + // Write the data to the sheet + for i, item := range nodeInfoResults { + if i == 0 { + for j, c := range item.Children { + f.SetCellValue(constant.NodeInfo, fmt.Sprintf("%c1", 'A'+rune(j)), c.Text) + } + } else { + for j, c := range item.Children { + f.SetCellValue(constant.NodeInfo, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + } + + // Create a new sheet for serviceconnect + if resultData.Spec.ServiceConnectResult != nil { + _, err := f.NewSheet(constant.ServiceConnect) + if err != nil { + return err + } + + serviceConnectResults := GetServiceConnect(resultData.Spec.ServiceConnectResult) + // Write the data to the sheet + for i, item := range serviceConnectResults { + if i == 0 { + for j, c := range item.Children { + f.SetCellValue(constant.ServiceConnect, fmt.Sprintf("%c1", 'A'+rune(j)), c.Text) + } + } else { + for j, c := range item.Children { + f.SetCellValue(constant.ServiceConnect, fmt.Sprintf("%c%d", 'A'+rune(j), i+1), c.Text) + } + } + } + } + f.DeleteSheet("Sheet1") + if err := f.SaveAs(fmt.Sprintf("%s.xlsx", path.Join(constant.ResultPathPrefix, resultData.Name))); err != nil { + return err + } + + return nil +} diff --git a/pkg/output/html.go b/pkg/output/html.go index 8e7dd4cc..5dfc3f9a 100644 --- a/pkg/output/html.go +++ b/pkg/output/html.go @@ -6,6 +6,7 @@ import ( "github.com/kubesphere/kubeeye/pkg/constant" "github.com/kubesphere/kubeeye/pkg/utils" "io" + corev1 "k8s.io/api/core/v1" "os" "path" "strings" @@ -40,41 +41,41 @@ func HtmlOut(resultName string) (error, map[string]interface{}) { var resultCollection = make(map[string][]renderNode, 5) if results.Spec.OpaResult.ResourceResults != nil { - list := getOpaList(results.Spec.OpaResult.ResourceResults) + list := GetOpaList(results.Spec.OpaResult.ResourceResults) resultCollection[constant.Opa] = list } if results.Spec.PrometheusResult != nil { - prometheus := getPrometheus(results.Spec.PrometheusResult) + prometheus := GetPrometheus(results.Spec.PrometheusResult) resultCollection[constant.Prometheus] = prometheus } if results.Spec.FileChangeResult != nil { - resultCollection[constant.FileChange] = getFileChange(results.Spec.FileChangeResult) + resultCollection[constant.FileChange] = GetFileChange(results.Spec.FileChangeResult) } if results.Spec.SysctlResult != nil { - resultCollection[constant.Sysctl] = getSysctl(results.Spec.SysctlResult) + resultCollection[constant.Sysctl] = GetSysctl(results.Spec.SysctlResult) } if results.Spec.SystemdResult != nil { - resultCollection[constant.Systemd] = getSystemd(results.Spec.SystemdResult) + resultCollection[constant.Systemd] = GetSystemd(results.Spec.SystemdResult) } if results.Spec.FileFilterResult != nil { - resultCollection[constant.FileFilter] = getFileFilter(results.Spec.FileFilterResult) + resultCollection[constant.FileFilter] = GetFileFilter(results.Spec.FileFilterResult) } if results.Spec.CommandResult != nil { - resultCollection[constant.CustomCommand] = getCommand(results.Spec.CommandResult) + resultCollection[constant.CustomCommand] = GetCommand(results.Spec.CommandResult) } if results.Spec.NodeInfo != nil { - resultCollection[constant.NodeInfo] = getNodeInfo(results.Spec.NodeInfo) + resultCollection[constant.NodeInfo] = GetNodeInfo(results.Spec.NodeInfo) } if results.Spec.ServiceConnectResult != nil { - component := getServiceConnect(results.Spec.ServiceConnectResult) + component := GetServiceConnect(results.Spec.ServiceConnectResult) resultCollection[constant.ServiceConnect] = component } @@ -100,7 +101,7 @@ func HtmlOut(resultName string) (error, map[string]interface{}) { return nil, data } -func getOpaList(result []v1alpha2.ResourceResult) (opaList []renderNode) { +func GetOpaList(result []v1alpha2.ResourceResult) (opaList []renderNode) { opaList = append(opaList, renderNode{Header: true, Children: []renderNode{ {Text: "Name"}, {Text: "Kind"}, {Text: "NameSpace"}, {Text: "Message"}, {Text: "Reason"}, {Text: "Level"}, }}) @@ -122,7 +123,7 @@ func getOpaList(result []v1alpha2.ResourceResult) (opaList []renderNode) { return opaList } -func getPrometheus(pro []v1alpha2.PrometheusResult) []renderNode { +func GetPrometheus(pro []v1alpha2.PrometheusResult) []renderNode { var prometheus []renderNode for _, p := range pro { value := renderNode{} @@ -132,7 +133,7 @@ func getPrometheus(pro []v1alpha2.PrometheusResult) []renderNode { return prometheus } -func getFileChange(fileChange []v1alpha2.FileChangeResultItem) []renderNode { +func GetFileChange(fileChange []v1alpha2.FileChangeResultItem) []renderNode { var villeinage []renderNode header := renderNode{Header: true, Children: []renderNode{ @@ -163,7 +164,7 @@ func getFileChange(fileChange []v1alpha2.FileChangeResultItem) []renderNode { return villeinage } -func getFileFilter(fileResult []v1alpha2.FileChangeResultItem) []renderNode { +func GetFileFilter(fileResult []v1alpha2.FileChangeResultItem) []renderNode { var villeinage []renderNode header := renderNode{Header: true, Children: []renderNode{ {Text: "name"}, @@ -184,7 +185,7 @@ func getFileFilter(fileResult []v1alpha2.FileChangeResultItem) []renderNode { return villeinage } -func getServiceConnect(component []v1alpha2.ServiceConnectResultItem) []renderNode { +func GetServiceConnect(component []v1alpha2.ServiceConnectResultItem) []renderNode { var villeinage []renderNode header := renderNode{Header: true, Children: []renderNode{ {Text: "name"}, @@ -203,7 +204,7 @@ func getServiceConnect(component []v1alpha2.ServiceConnectResultItem) []renderNo return villeinage } -func getSysctl(sysctlResult []v1alpha2.NodeMetricsResultItem) []renderNode { +func GetSysctl(sysctlResult []v1alpha2.NodeMetricsResultItem) []renderNode { var villeinage []renderNode header := renderNode{Header: true, Children: []renderNode{ @@ -230,7 +231,7 @@ func getSysctl(sysctlResult []v1alpha2.NodeMetricsResultItem) []renderNode { return villeinage } -func getNodeInfo(nodeInfo []v1alpha2.NodeInfoResultItem) []renderNode { +func GetNodeInfo(nodeInfo []v1alpha2.NodeInfoResultItem) []renderNode { var villeinage []renderNode header := renderNode{Header: true, Children: []renderNode{ @@ -261,7 +262,7 @@ func getNodeInfo(nodeInfo []v1alpha2.NodeInfoResultItem) []renderNode { return villeinage } -func getSystemd(systemdResult []v1alpha2.NodeMetricsResultItem) []renderNode { +func GetSystemd(systemdResult []v1alpha2.NodeMetricsResultItem) []renderNode { var villeinage []renderNode header := renderNode{Header: true, Children: []renderNode{ @@ -287,7 +288,7 @@ func getSystemd(systemdResult []v1alpha2.NodeMetricsResultItem) []renderNode { return villeinage } -func getCommand(commandResult []v1alpha2.CommandResultItem) []renderNode { +func GetCommand(commandResult []v1alpha2.CommandResultItem) []renderNode { var villeinage []renderNode header := renderNode{Header: true, Children: []renderNode{ @@ -313,3 +314,111 @@ func getCommand(commandResult []v1alpha2.CommandResultItem) []renderNode { return villeinage } + +func GetNodesStatus(result *corev1.NodeList) (nodeList []renderNode) { + nodeList = append(nodeList, renderNode{Header: true, Children: []renderNode{ + {Text: "Name"}, {Text: "Address"}, {Text: "Version"}, {Text: "Status"}, + }}) + for _, resourceResult := range result.Items { + address := "" + status := "" + for _, addr := range resourceResult.Status.Addresses { + if addr.Type == corev1.NodeInternalIP { + address = addr.Address + } + } + for _, condition := range resourceResult.Status.Conditions { + if condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue { + status = "Ready" + } else { + status = "NotReady" + } + } + items := []renderNode{ + {Text: resourceResult.Name}, + {Text: address}, + {Text: resourceResult.Status.NodeInfo.KubeletVersion}, + {Text: status}, + } + + nodeList = append(nodeList, renderNode{Children: items}) + } + + return nodeList +} + +func GetAbnormalPods(result *corev1.PodList) (podList []renderNode) { + podList = append(podList, renderNode{Header: true, Children: []renderNode{ + {Text: "Name"}, {Text: "Namespace"}, {Text: "Phase"}, {Text: "Reason"}, {Text: "Message"}, + }}) + for _, resourceResult := range result.Items { + if resourceResult.Status.Phase != corev1.PodRunning && resourceResult.Status.Phase != corev1.PodSucceeded { + reason := "" + message := "" + if len(resourceResult.Status.InitContainerStatuses) > 0 { + for _, intContainer := range resourceResult.Status.InitContainerStatuses { + if intContainer.State.Waiting != nil { + if reason != "" { + reason = reason + ", " + intContainer.State.Waiting.Reason + } else { + reason = intContainer.State.Waiting.Reason + } + if message != "" { + message = message + "\n" + intContainer.State.Waiting.Message + } else { + message = intContainer.State.Waiting.Message + } + } else if intContainer.State.Terminated != nil { + if reason != "" { + reason = reason + ", " + intContainer.State.Terminated.Reason + } else { + reason = intContainer.State.Terminated.Reason + } + if message != "" { + message = message + "\n" + intContainer.State.Terminated.Message + } else { + message = intContainer.State.Terminated.Message + } + } + } + } + if len(resourceResult.Status.ContainerStatuses) > 0 { + for _, container := range resourceResult.Status.ContainerStatuses { + if container.State.Waiting != nil { + if reason != "" { + reason = reason + ", " + container.State.Waiting.Reason + } else { + reason = container.State.Waiting.Reason + } + if message != "" { + message = message + "\n" + container.State.Waiting.Message + } else { + message = container.State.Waiting.Message + } + } else if container.State.Terminated != nil { + if reason != "" { + reason = reason + ", " + container.State.Terminated.Reason + } else { + reason = container.State.Terminated.Reason + } + if message != "" { + message = message + "\n" + container.State.Terminated.Message + } else { + message = container.State.Terminated.Message + } + } + } + } + items := []renderNode{ + {Text: resourceResult.Name}, + {Text: resourceResult.Namespace}, + {Text: string(resourceResult.Status.Phase)}, + {Text: reason}, + {Text: message}, + } + podList = append(podList, renderNode{Children: items}) + } + } + + return podList +} diff --git a/pkg/server/api/inspectResult.go b/pkg/server/api/inspectResult.go index f5dc558d..aceb522d 100644 --- a/pkg/server/api/inspectResult.go +++ b/pkg/server/api/inspectResult.go @@ -121,6 +121,13 @@ func (i *InspectResult) GetInspectResult(gin *gin.Context) { return } gin.HTML(http.StatusOK, template.InspectResultTemplate, m) + case "json": + data, err := i.GetFileResultData(name) + if err != nil { + gin.JSON(http.StatusInternalServerError, NewErrors(err.Error(), "InspectResult")) + return + } + gin.JSON(http.StatusOK, data) case "customized": data, err := i.GetFileResultData(name) if err != nil { @@ -155,6 +162,16 @@ func (i *InspectResult) GetInspectResult(gin *gin.Context) { } +func (i *InspectResult) DownloadInspectResult(c *gin.Context) { + name := c.Param("name") + filePath := path.Join(constant.ResultPathPrefix, name) + c.Header("Content-Type", "application/octet-stream") + c.Header("Content-Disposition", "attachment; filename="+path.Base(filePath)) + c.Header("Content-Transfer-Encoding", "binary") + + c.File(path.Join(constant.ResultPathPrefix, name+".xlsx")) +} + func (i *InspectResult) compare(a, b map[string]interface{}, orderBy string) bool { left := utils.MapToStruct[v1alpha2.InspectResult](a) right := utils.MapToStruct[v1alpha2.InspectResult](b) diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index 076a4686..f34ee932 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -32,6 +32,7 @@ func RegisterRouter(ctx context.Context, r *gin.Engine, clients *kube.Kubernetes v1alpha1.GET("/inspectresults", result.ListInspectResult) v1alpha1.GET("/inspectresults/:name", result.GetInspectResult) + v1alpha1.GET("/inspectresults/:name/download", result.DownloadInspectResult) v1alpha1.GET("/inspecttasks", task.ListInspectTask) v1alpha1.GET("/inspecttasks/:name", task.GetInspectTask)