Skip to content

Commit

Permalink
Merge pull request #12 from reactiveops/jg/add-deploys
Browse files Browse the repository at this point in the history
jg/add deploys
  • Loading branch information
JessicaGreben authored Jan 28, 2019
2 parents 8665fd5 + 31897ee commit af858e8
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 95 deletions.
21 changes: 18 additions & 3 deletions deploy/all.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
---
kind: Service
apiVersion: v1
metadata:
name: fairwinds-dash
spec:
selector:
app: fairwinds
ports:
- name: dashboard
protocol: TCP
port: 80
targetPort: 8080
---
apiVersion: v1
kind: Namespace
metadata:
Expand All @@ -22,9 +35,9 @@ metadata:
app: fairwinds
rules:
- apiGroups:
- ''
- 'apps/v1'
- 'admissionregistration.k8s.io'
- ''
- 'apps'
- 'admissionregistration.k8s.io'
resources:
- '*'
verbs:
Expand Down Expand Up @@ -110,6 +123,8 @@ spec:
image: quay.io/reactiveops/fairwinds
command: ["fairwinds", "--webhook"]
imagePullPolicy: Always
ports:
- containerPort: 9876
resources:
limits:
cpu: 100m
Expand Down
42 changes: 6 additions & 36 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,22 @@
package main

import (
"encoding/json"
"flag"
glog "log"
"net/http"
"os"

conf "github.com/reactiveops/fairwinds/pkg/config"
"github.com/reactiveops/fairwinds/pkg/kube"
"github.com/reactiveops/fairwinds/pkg/validator"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apitypes "k8s.io/apimachinery/pkg/types"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder"
)

// FairwindsName is used for Kubernetes resource naming
Expand Down Expand Up @@ -66,27 +62,12 @@ func main() {
}

func startDashboardServer(c conf.Configuration) {
http.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) { validateHandler(w, r, c) })
http.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) { validator.DeployHandler(w, r, c) })
http.HandleFunc("/ping", validator.PingHandler)
glog.Println("Starting Fairwinds dashboard webserver on port 8080.")
glog.Fatal(http.ListenAndServe(":8080", nil))
}

func validateHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration) {
var results []validator.Results
pods, err := kube.CoreV1API.Pods("").List(metav1.ListOptions{})
if err != nil {
return
}
glog.Println("pods count:", len(pods.Items))
for _, pod := range pods.Items {
result := validator.ValidatePods(c, &pod, validator.Results{})
results = append(results, result)
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(results)
}

func startWebhookServer(c conf.Configuration, disableWebhookConfigInstaller bool) {
logf.SetLogger(logf.ZapLogger(false))
entryLog := log.WithName("entrypoint")
Expand All @@ -99,19 +80,6 @@ func startWebhookServer(c conf.Configuration, disableWebhookConfigInstaller bool
os.Exit(1)
}

podValidatingWebhook, err := builder.NewWebhookBuilder().
Name("validating.k8s.io").
Validating().
Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update).
WithManager(mgr).
ForType(&corev1.Pod{}).
Handlers(&validator.PodValidator{Config: c}).
Build()
if err != nil {
entryLog.Error(err, "unable to setup validating webhook")
os.Exit(1)
}

entryLog.Info("setting up webhook server")
as, err := webhook.NewServer(FairwindsName, mgr, webhook.ServerOptions{
Port: 9876,
Expand Down Expand Up @@ -140,8 +108,10 @@ func startWebhookServer(c conf.Configuration, disableWebhookConfigInstaller bool
os.Exit(1)
}

p := validator.NewWebhook("pod", mgr, validator.Validator{Config: c}, &corev1.Pod{})
d := validator.NewWebhook("deploy", mgr, validator.Validator{Config: c}, &appsv1.Deployment{})
entryLog.Info("registering webhooks to the webhook server")
if err = as.Register(podValidatingWebhook); err != nil {
if err = as.Register(p, d); err != nil {
entryLog.Error(err, "unable to register webhooks in the admission server")
os.Exit(1)
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/kube/clientset.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ var clientset = createClientset()

// CoreV1API exports the v1 Core API client.
var CoreV1API = clientset.CoreV1()

// AppsV1API exports the v1 Apps API client.
var AppsV1API = clientset.AppsV1()
70 changes: 70 additions & 0 deletions pkg/validator/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package validator

import (
"context"
"net/http"

conf "github.com/reactiveops/fairwinds/pkg/config"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
)

// Validator validates k8s resources.
type Validator struct {
client client.Client
decoder types.Decoder
Config conf.Configuration
}

var _ inject.Client = &Validator{}

// InjectClient injects the client.
func (v *Validator) InjectClient(c client.Client) error {
v.client = c
return nil
}

var _ inject.Decoder = &Validator{}

// InjectDecoder injects the decoder.
func (v *Validator) InjectDecoder(d types.Decoder) error {
v.decoder = d
return nil
}

var _ admission.Handler = &Validator{}

// Handle for Validator to run validation checks.
func (v *Validator) Handle(ctx context.Context, req types.Request) types.Response {
var err error
var allowed bool
var reason string
var results Results

switch req.AdmissionRequest.Kind.Kind {
case "Deployment":
deploy := appsv1.Deployment{}
err = v.decoder.Decode(req, &deploy)
results = ValidateDeploys(v.Config, &deploy, Results{})
case "Pod":
pod := corev1.Pod{}
err = v.decoder.Decode(req, &pod)
results = ValidatePods(v.Config, &pod.Spec, Results{})
}
if err != nil {
return admission.ErrorResponse(http.StatusBadRequest, err)
}

allowed, reason = results.Format()
return admission.ValidationResponse(allowed, reason)
}

// ValidateDeploys validates that each deployment conforms to the Fairwinds config.
func ValidateDeploys(conf conf.Configuration, deploy *appsv1.Deployment, results Results) Results {
pod := deploy.Spec.Template.Spec
return ValidatePods(conf, &pod, results)
}
33 changes: 33 additions & 0 deletions pkg/validator/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package validator

import (
"encoding/json"
"fmt"
"net/http"

conf "github.com/reactiveops/fairwinds/pkg/config"
"github.com/reactiveops/fairwinds/pkg/kube"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// PingHandler is an endpoint to check the server is up.
func PingHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "pong\n")
}

// DeployHandler creates a handler for to validate the current deploy workloads.
func DeployHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration) {
var results []Results
deploys, err := kube.AppsV1API.Deployments("").List(metav1.ListOptions{})
if err != nil {
fmt.Fprintf(w, err.Error())
return
}
for _, deploy := range deploys.Items {
result := ValidateDeploys(c, &deploy, Results{})
results = append(results, result)
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(results)
}
60 changes: 4 additions & 56 deletions pkg/validator/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,55 +15,23 @@
package validator

import (
"context"
"net/http"

conf "github.com/reactiveops/fairwinds/pkg/config"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
)

var log = logf.Log.WithName("Fairwinds Validator")

// PodValidator validates Pods
type PodValidator struct {
client client.Client
decoder types.Decoder
Config conf.Configuration
}

// Implement admission.Handler so the controller can handle admission request.
var _ admission.Handler = &PodValidator{}

// Handle for PodValidator admits a pod if validation passes.
func (v *PodValidator) Handle(ctx context.Context, req types.Request) types.Response {
pod := &corev1.Pod{}

err := v.decoder.Decode(req, pod)
if err != nil {
return admission.ErrorResponse(http.StatusBadRequest, err)
}

results := ValidatePods(v.Config, pod, Results{})
allowed, reason := results.Format()

return admission.ValidationResponse(allowed, reason)
}

// ValidatePods does validates that each pod conforms to the Fairwinds config.
func ValidatePods(conf conf.Configuration, pod *corev1.Pod, results Results) Results {
for _, container := range pod.Spec.InitContainers {
// ValidatePods validates that each pod conforms to the Fairwinds config.
func ValidatePods(conf conf.Configuration, pod *corev1.PodSpec, results Results) Results {
for _, container := range pod.InitContainers {
results.InitContainerValidations = append(
results.InitContainerValidations,
validateContainer(conf, container),
)
}

for _, container := range pod.Spec.Containers {
for _, container := range pod.Containers {
results.ContainerValidations = append(
results.ContainerValidations,
validateContainer(conf, container),
Expand All @@ -72,23 +40,3 @@ func ValidatePods(conf conf.Configuration, pod *corev1.Pod, results Results) Res

return results
}

// PodValidator implements inject.Client.
// A client will be automatically injected.
var _ inject.Client = &PodValidator{}

// InjectClient injects the client.
func (v *PodValidator) InjectClient(c client.Client) error {
v.client = c
return nil
}

// PodValidator implements inject.Decoder.
// A decoder will be automatically injected.
var _ inject.Decoder = &PodValidator{}

// InjectDecoder injects the decoder.
func (v *PodValidator) InjectDecoder(d types.Decoder) error {
v.decoder = d
return nil
}
34 changes: 34 additions & 0 deletions pkg/validator/webhooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package validator

import (
"fmt"
"os"

admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder"
)

// NewWebhook creates a validating admission webhook for the apiType.
func NewWebhook(name string, mgr manager.Manager, validator Validator, apiType runtime.Object) *admission.Webhook {
name = fmt.Sprintf("%s.k8s.io", name)
path := fmt.Sprintf("/validating-%s", name)

webhook, err := builder.NewWebhookBuilder().
Name(name).
Validating().
Path(path).
Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update).
WithManager(mgr).
ForType(apiType).
Handlers(&validator).
Build()
if err != nil {
log.Error(err, "unable to setup validating webhook:", name)
os.Exit(1)
}

return webhook
}
22 changes: 22 additions & 0 deletions test/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ng
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
File renamed without changes.

0 comments on commit af858e8

Please sign in to comment.