mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-05-08 18:26:43 +00:00
Add audit mode to CLI
Add option to send audit results to a remote host add audit flag to print results to stdout add comments make comments more consistent move audit test fix fullaudit_test add test instructions to README update audit test simplify stdout output update comment fix import run audit by default
This commit is contained in:
@@ -39,3 +39,10 @@ helm upgrade --install fairwinds charts/fairwinds/ --namespace fairwinds --recre
|
||||
kubectl port-forward --namespace fairwinds svc/fairwinds-fairwinds-dashboard 8080:80 &
|
||||
open http://localhost:8080
|
||||
```
|
||||
|
||||
## Run tests
|
||||
```
|
||||
go list ./ | grep -v vendor | xargs golint -set_exit_status
|
||||
go list ./ | grep -v vendor | xargs go vet
|
||||
go test ./pkg/... -v -coverprofile cover.out
|
||||
```
|
||||
|
||||
55
main.go
55
main.go
@@ -15,6 +15,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@@ -26,7 +28,9 @@ import (
|
||||
conf "github.com/reactiveops/fairwinds/pkg/config"
|
||||
"github.com/reactiveops/fairwinds/pkg/dashboard"
|
||||
"github.com/reactiveops/fairwinds/pkg/kube"
|
||||
"github.com/reactiveops/fairwinds/pkg/validator"
|
||||
fwebhook "github.com/reactiveops/fairwinds/pkg/webhook"
|
||||
"gopkg.in/yaml.v2"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
@@ -43,8 +47,10 @@ var log = logf.Log.WithName("fairwinds")
|
||||
func main() {
|
||||
dashboard := flag.Bool("dashboard", false, "Runs the webserver for Fairwinds dashboard.")
|
||||
webhook := flag.Bool("webhook", false, "Runs the webhook webserver.")
|
||||
audit := flag.Bool("audit", false, "Runs a one-time audit.")
|
||||
dashboardPort := flag.Int("dashboard-port", 8080, "Port for the dashboard webserver")
|
||||
webhookPort := flag.Int("webhook-port", 9876, "Port for the webhook webserver")
|
||||
auditDestination := flag.String("audit-destination", "", "Destination URL to send audit results (prints to stdout if unspecified)")
|
||||
|
||||
var disableWebhookConfigInstaller bool
|
||||
flag.BoolVar(&disableWebhookConfigInstaller, "disable-webhook-config-installer", false,
|
||||
@@ -58,17 +64,16 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !*dashboard && !*webhook && !*audit {
|
||||
*audit = true
|
||||
}
|
||||
|
||||
if *webhook {
|
||||
startWebhookServer(c, disableWebhookConfigInstaller, *webhookPort)
|
||||
}
|
||||
|
||||
if *dashboard {
|
||||
} else if *dashboard {
|
||||
startDashboardServer(c, *dashboardPort)
|
||||
}
|
||||
|
||||
if !*dashboard && !*webhook {
|
||||
glog.Println("Must specify either -webhook, -dashboard, or both")
|
||||
os.Exit(1)
|
||||
} else if *audit {
|
||||
runAudit(c, *auditDestination)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,3 +159,37 @@ func startWebhookServer(c conf.Configuration, disableWebhookConfigInstaller bool
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func runAudit(c conf.Configuration, destination string) {
|
||||
k, _ := kube.CreateKubeAPI()
|
||||
auditData, err := validator.RunAudit(c, k)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if destination != "" {
|
||||
jsonData, err := json.Marshal(auditData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", destination, bytes.NewBuffer(jsonData))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
glog.Println(string(body))
|
||||
} else {
|
||||
y, err := yaml.Marshal(auditData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(y))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,9 @@ const (
|
||||
TemplateFile = "pkg/dashboard/templates/" + TemplateName
|
||||
)
|
||||
|
||||
// TemplateData represents data in a format that's template friendly.
|
||||
type TemplateData struct {
|
||||
ClusterSummary *validator.ResultSummary
|
||||
NamespacedResults validator.NamespacedResults
|
||||
}
|
||||
|
||||
// MainHandler gets template data and renders the dashboard with it.
|
||||
func MainHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, kubeAPI *kube.API) {
|
||||
templateData, err := getTemplateData(c, kubeAPI)
|
||||
templateData, err := validator.RunAudit(c, kubeAPI)
|
||||
if err != nil {
|
||||
http.Error(w, "Error Fetching Deployments", 500)
|
||||
return
|
||||
@@ -62,7 +56,7 @@ func MainHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, k
|
||||
|
||||
// EndpointHandler gets template data and renders json with it.
|
||||
func EndpointHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, kubeAPI *kube.API) {
|
||||
templateData, err := getTemplateData(c, kubeAPI)
|
||||
templateData, err := validator.RunAudit(c, kubeAPI)
|
||||
if err != nil {
|
||||
http.Error(w, "Error Fetching Deployments", 500)
|
||||
return
|
||||
@@ -72,36 +66,3 @@ func EndpointHandler(w http.ResponseWriter, r *http.Request, c conf.Configuratio
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(templateData)
|
||||
}
|
||||
|
||||
func getTemplateData(config conf.Configuration, kubeAPI *kube.API) (TemplateData, error) {
|
||||
|
||||
// TODO: Once we are validating more than deployments,
|
||||
// we will need to merge the namespaceResults that get returned
|
||||
// from each validation.
|
||||
nsResults, err := validator.ValidateDeploys(config, kubeAPI)
|
||||
if err != nil {
|
||||
return TemplateData{}, err
|
||||
}
|
||||
|
||||
var clusterSuccesses, clusterErrors, clusterWarnings uint
|
||||
|
||||
// Aggregate all summary counts to get a clusterwide count.
|
||||
for _, nsRes := range nsResults {
|
||||
for _, rr := range nsRes.Results {
|
||||
clusterErrors += rr.Summary.Errors
|
||||
clusterWarnings += rr.Summary.Warnings
|
||||
clusterSuccesses += rr.Summary.Successes
|
||||
}
|
||||
}
|
||||
|
||||
templateData := TemplateData{
|
||||
ClusterSummary: &validator.ResultSummary{
|
||||
Errors: clusterErrors,
|
||||
Warnings: clusterWarnings,
|
||||
Successes: clusterSuccesses,
|
||||
},
|
||||
NamespacedResults: nsResults,
|
||||
}
|
||||
|
||||
return templateData, nil
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
conf "github.com/reactiveops/fairwinds/pkg/config"
|
||||
"github.com/reactiveops/fairwinds/pkg/validator"
|
||||
"github.com/reactiveops/fairwinds/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetTemplateData(t *testing.T) {
|
||||
k8s := test.SetupTestAPI()
|
||||
k8s = test.SetupAddDeploys(k8s, "test")
|
||||
|
||||
c := conf.Configuration{
|
||||
HealthChecks: conf.HealthChecks{
|
||||
ReadinessProbeMissing: conf.SeverityError,
|
||||
LivenessProbeMissing: conf.SeverityWarning,
|
||||
},
|
||||
}
|
||||
|
||||
sum := &validator.ResultSummary{
|
||||
Successes: uint(4),
|
||||
Warnings: uint(1),
|
||||
Errors: uint(1),
|
||||
}
|
||||
|
||||
actualTmplData, _ := getTemplateData(c, k8s)
|
||||
|
||||
assert.EqualValues(t, actualTmplData.ClusterSummary, sum)
|
||||
assert.Equal(t, len(actualTmplData.NamespacedResults["test"].Results), 1, "should be equal")
|
||||
assert.Equal(t, len(actualTmplData.NamespacedResults["test"].Results[0].PodResults), 1, "should be equal")
|
||||
assert.Equal(t, len(actualTmplData.NamespacedResults["test"].Results[0].PodResults[0].ContainerResults), 1, "should be equal")
|
||||
assert.Equal(t, len(actualTmplData.NamespacedResults["test"].Results[0].PodResults[0].ContainerResults[0].Messages), 6, "should be equal")
|
||||
}
|
||||
47
pkg/validator/fullaudit.go
Normal file
47
pkg/validator/fullaudit.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
conf "github.com/reactiveops/fairwinds/pkg/config"
|
||||
"github.com/reactiveops/fairwinds/pkg/kube"
|
||||
)
|
||||
|
||||
// AuditData contains all the data from a full Fairwinds audit
|
||||
type AuditData struct {
|
||||
ClusterSummary ResultSummary
|
||||
NamespacedResults NamespacedResults
|
||||
}
|
||||
|
||||
// RunAudit runs a full Fairwinds audit and returns an AuditData object
|
||||
func RunAudit(config conf.Configuration, kubeAPI *kube.API) (AuditData, error) {
|
||||
// TODO: Validate StatefulSets, DaemonSets, Cron jobs
|
||||
// in addition to deployments
|
||||
|
||||
// TODO: Once we are validating more than deployments,
|
||||
// we will need to merge the namespaceResults that get returned
|
||||
// from each validation.
|
||||
nsResults, err := ValidateDeploys(config, kubeAPI)
|
||||
if err != nil {
|
||||
return AuditData{}, err
|
||||
}
|
||||
|
||||
var clusterSuccesses, clusterErrors, clusterWarnings uint
|
||||
|
||||
// Aggregate all summary counts to get a clusterwide count.
|
||||
for _, nsRes := range nsResults {
|
||||
for _, rr := range nsRes.Results {
|
||||
clusterErrors += rr.Summary.Errors
|
||||
clusterWarnings += rr.Summary.Warnings
|
||||
clusterSuccesses += rr.Summary.Successes
|
||||
}
|
||||
}
|
||||
|
||||
auditData := AuditData{
|
||||
ClusterSummary: ResultSummary{
|
||||
Errors: clusterErrors,
|
||||
Warnings: clusterWarnings,
|
||||
Successes: clusterSuccesses,
|
||||
},
|
||||
NamespacedResults: nsResults,
|
||||
}
|
||||
return auditData, nil
|
||||
}
|
||||
36
pkg/validator/fullaudit_test.go
Normal file
36
pkg/validator/fullaudit_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
conf "github.com/reactiveops/fairwinds/pkg/config"
|
||||
"github.com/reactiveops/fairwinds/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetTemplateData(t *testing.T) {
|
||||
k8s := test.SetupTestAPI()
|
||||
k8s = test.SetupAddDeploys(k8s, "test")
|
||||
|
||||
c := conf.Configuration{
|
||||
HealthChecks: conf.HealthChecks{
|
||||
ReadinessProbeMissing: conf.SeverityError,
|
||||
LivenessProbeMissing: conf.SeverityWarning,
|
||||
},
|
||||
}
|
||||
|
||||
sum := ResultSummary{
|
||||
Successes: uint(4),
|
||||
Warnings: uint(1),
|
||||
Errors: uint(1),
|
||||
}
|
||||
|
||||
actualAudit, err := RunAudit(c, k8s)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
|
||||
assert.EqualValues(t, actualAudit.ClusterSummary, sum)
|
||||
assert.Equal(t, len(actualAudit.NamespacedResults["test"].Results), 1, "should be equal")
|
||||
assert.Equal(t, len(actualAudit.NamespacedResults["test"].Results[0].PodResults), 1, "should be equal")
|
||||
assert.Equal(t, len(actualAudit.NamespacedResults["test"].Results[0].PodResults[0].ContainerResults), 1, "should be equal")
|
||||
assert.Equal(t, len(actualAudit.NamespacedResults["test"].Results[0].PodResults[0].ContainerResults[0].Messages), 6, "should be equal")
|
||||
}
|
||||
Reference in New Issue
Block a user