diff --git a/main.go b/main.go index 14c1371d2..79a081387 100644 --- a/main.go +++ b/main.go @@ -59,6 +59,7 @@ func main() { auditOutputURL := flag.String("output-url", "", "Destination URL to send audit results") auditOutputFile := flag.String("output-file", "", "Destination file for audit results") auditOutputFormat := flag.String("output-format", "json", "Output format for results - json, yaml, or score") + loadAuditFile := flag.String("load-audit-file", "", "Runs the dashboard with data saved from a past audit.") displayName := flag.String("display-name", "", "An optional identifier for the audit") configPath := flag.String("config", "", "Location of Polaris configuration file") logLevel := flag.String("log-level", logrus.InfoLevel.String(), "Logrus log level") @@ -103,7 +104,7 @@ func main() { if *webhook { startWebhookServer(c, *disableWebhookConfigInstaller, *webhookPort) } else if *dashboard { - startDashboardServer(c, *auditPath, *dashboardPort, *dashboardBasePath) + startDashboardServer(c, *auditPath, *loadAuditFile, *dashboardPort, *dashboardBasePath) } else if *audit { auditData := runAndReportAudit(c, *auditPath, *auditOutputFile, *auditOutputURL, *auditOutputFormat) @@ -118,8 +119,12 @@ func main() { } } -func startDashboardServer(c conf.Configuration, auditPath string, port int, basePath string) { - router := dashboard.GetRouter(c, auditPath, port, basePath) +func startDashboardServer(c conf.Configuration, auditPath string, loadAuditFile string, port int, basePath string) { + var auditData validator.AuditData + if loadAuditFile != "" { + auditData = validator.ReadAuditFromFile(loadAuditFile) + } + router := dashboard.GetRouter(c, auditPath, port, basePath, &auditData) router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("OK")) }) diff --git a/pkg/dashboard/dashboard.go b/pkg/dashboard/dashboard.go index 711d5bc04..0653d6b05 100644 --- a/pkg/dashboard/dashboard.go +++ b/pkg/dashboard/dashboard.go @@ -21,11 +21,11 @@ import ( "net/http" "strings" - packr "github.com/gobuffalo/packr/v2" - "github.com/gorilla/mux" conf "github.com/fairwindsops/polaris/pkg/config" "github.com/fairwindsops/polaris/pkg/kube" "github.com/fairwindsops/polaris/pkg/validator" + packr "github.com/gobuffalo/packr/v2" + "github.com/gorilla/mux" "github.com/sirupsen/logrus" "gitlab.com/golang-commonmark/markdown" ) @@ -136,7 +136,7 @@ func writeTemplate(tmpl *template.Template, data *templateData, w http.ResponseW } // GetRouter returns a mux router serving all routes necessary for the dashboard -func GetRouter(c conf.Configuration, auditPath string, port int, basePath string) *mux.Router { +func GetRouter(c conf.Configuration, auditPath string, port int, basePath string, auditData *validator.AuditData) *mux.Router { router := mux.NewRouter() router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("OK")) @@ -151,13 +151,24 @@ func GetRouter(c conf.Configuration, auditPath string, port int, basePath string w.Write(favicon) }) router.HandleFunc("/results.json", func(w http.ResponseWriter, r *http.Request) { - k, err := kube.CreateResourceProvider(auditPath) - if err != nil { - logrus.Errorf("Error fetching Kubernetes resources %v", err) - http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError) - return + if auditData == nil { + k, err := kube.CreateResourceProvider(auditPath) + if err != nil { + logrus.Errorf("Error fetching Kubernetes resources %v", err) + http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError) + return + } + + auditDataObj, err := validator.RunAudit(c, k) + if err != nil { + http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError) + return + } + auditData = &auditDataObj } - JSONHandler(w, r, c, k) + + JSONHandler(w, r, auditData) + }) router.HandleFunc("/details/{category}", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -178,13 +189,19 @@ func GetRouter(c conf.Configuration, auditPath string, port int, basePath string http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError) return } - auditData, err := validator.RunAudit(c, k) - if err != nil { - logrus.Errorf("Error getting audit data: %v", err) - http.Error(w, "Error running audit", 500) - return + if auditData == nil { + auditData, err := validator.RunAudit(c, k) + + if err != nil { + logrus.Errorf("Error getting audit data: %v", err) + http.Error(w, "Error running audit", 500) + return + } + MainHandler(w, r, auditData, basePath) + } else { + MainHandler(w, r, *auditData, basePath) } - MainHandler(w, r, auditData, basePath) + }) return router } @@ -213,13 +230,7 @@ func MainHandler(w http.ResponseWriter, r *http.Request, auditData validator.Aud } // JSONHandler gets template data and renders json with it. -func JSONHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, kubeResources *kube.ResourceProvider) { - auditData, err := validator.RunAudit(c, kubeResources) - if err != nil { - http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError) - return - } - +func JSONHandler(w http.ResponseWriter, r *http.Request, auditData *validator.AuditData) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(auditData) diff --git a/pkg/validator/types.go b/pkg/validator/types.go index 91d5f45ef..7eeacc446 100644 --- a/pkg/validator/types.go +++ b/pkg/validator/types.go @@ -15,11 +15,18 @@ package validator import ( + "bytes" "fmt" + "io" + "io/ioutil" + "os" + + "github.com/sirupsen/logrus" "github.com/fairwindsops/polaris/pkg/config" conf "github.com/fairwindsops/polaris/pkg/config" corev1 "k8s.io/api/core/v1" + apiMachineryYAML "k8s.io/apimachinery/pkg/util/yaml" ) // MessageType represents the type of Message @@ -197,3 +204,34 @@ type ResultMessage struct { Type MessageType Category string } + +// ReadAuditFromFile reads the data from a past audit stored in a JSON or YAML file. +func ReadAuditFromFile(fileName string) AuditData { + auditData := AuditData{} + oldFileBytes, err := ioutil.ReadFile(fileName) + if err != nil { + logrus.Errorf("Unable to read contents of loaded file: %v", err) + os.Exit(1) + } + auditData, err = ParseAudit(oldFileBytes) + if err != nil { + logrus.Errorf("Error parsing file contents into auditData: %v", err) + os.Exit(1) + } + return auditData +} + +// ParseAudit decodes either a YAML or JSON file and returns AuditData. +func ParseAudit(oldFileBytes []byte) (AuditData, error) { + reader := bytes.NewReader(oldFileBytes) + conf := AuditData{} + d := apiMachineryYAML.NewYAMLOrJSONDecoder(reader, 4096) + for { + if err := d.Decode(&conf); err != nil { + if err == io.EOF { + return conf, nil + } + return conf, fmt.Errorf("Decoding config failed: %v", err) + } + } +}