Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --audit and --audit-destination flags #40

Merged
merged 1 commit into from
Apr 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 47 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package main

import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
Expand All @@ -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"
Expand All @@ -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.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand this correctly, audit, webhook, and dashboard are all separate modes Fairwinds can run in, but it can never run in more than one of them. Is that accurate? If so, it might make more sense to have a single mode/service/whatever argument with a default value, maybe dashboard?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that you could run webhook and dashboard simultaneously. Is that wrong?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe that's currently possible due to them both requiring a server to run, and those servers being different implementations (one of which is specific to controller-runtime's webhook implementation). I think it could be possible to run them together at some point in the future, but it may be simplest to keep them separate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the case, it might make sense to split these operations into different binaries. I can take a crack at that in another PR if you agree.

For now, I set audit as the default, since that's the simplest operation. It'll also exit immediately, so if you intended a different operation you won't be sitting there waiting for it to finish.

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,
Expand All @@ -58,17 +64,16 @@ func main() {
os.Exit(1)
}

if *webhook {
startWebhookServer(c, disableWebhookConfigInstaller, *webhookPort)
if !*dashboard && !*webhook && !*audit {
*audit = true
}

if *dashboard {
if *webhook {
startWebhookServer(c, disableWebhookConfigInstaller, *webhookPort)
} 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)
}
}

Expand Down Expand Up @@ -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))
}
}
43 changes: 2 additions & 41 deletions pkg/dashboard/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
}
36 changes: 0 additions & 36 deletions pkg/dashboard/dashboard_test.go

This file was deleted.

47 changes: 47 additions & 0 deletions pkg/validator/fullaudit.go
Original file line number Diff line number Diff line change
@@ -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 changes: 36 additions & 0 deletions pkg/validator/fullaudit_test.go
Original file line number Diff line number Diff line change
@@ -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")
}