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

jg/add deploys #12

Merged
merged 6 commits into from
Jan 28, 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
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.