Skip to content

Commit

Permalink
fix: add webhook certs and install
Browse files Browse the repository at this point in the history
  • Loading branch information
aslakknutsen committed Oct 7, 2020
1 parent 1bd9bb5 commit f42ee6d
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 101 deletions.
64 changes: 64 additions & 0 deletions deploy/mutation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
kind: Template
apiVersion: template.openshift.io/v1
parameters:
- name: NAMESPACE
description: "Target namespace for which role binding should be defined"
required: true
value: istio-workspace-operator
objects:
- kind: Service
apiVersion: v1
metadata:
name: istio-workspace
annotations:
service.beta.openshift.io/serving-cert-secret-name: istio-workspace
labels:
app: istio-workspace
spec:
ports:
- port: 443
targetPort: 8443
selector:
app: istio-workspace
- kind: MutatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1
metadata:
name: istio-workspace
annotations:
service.beta.openshift.io/inject-cabundle: "true"
labels:
app: istio-workspace
webhooks:
- name: istio-workspace.${NAMESPACE}.svc.cluster.local
admissionReviewVersions: ["v1beta1"]
matchPolicy: Equivalent
clientConfig:
service:
name: istio-workspace
namespace: ${NAMESPACE}
path: "/deployment-mutation"
port: 443
caBundle: <CA_BUNDLE>
rules:
- operations: ["CREATE", "UPDATE", "DELETE"]
apiGroups: ["apps"]
apiVersions: ["v1"]
resources: ["deployments"]
scope: Namespaced
sideEffects: NoneOnDryRun
timeoutSeconds: 5
failurePolicy: Fail
- kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
labels:
app: istio-workspace
name: istio-workspace
spec:
ingress:
- {}
podSelector:
matchLabels:
app: istio-workspace
policyTypes:
- Ingress
9 changes: 9 additions & 0 deletions deploy/operator.tpl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ objects:
labels:
app: istio-workspace
version: ${IKE_VERSION}
maistra.io/expose-route: "true"
spec:
serviceAccountName: istio-workspace
containers:
Expand All @@ -55,6 +56,10 @@ objects:
args:
- serve
imagePullPolicy: Always
volumeMounts:
- name: certs
mountPath: /tmp/certs
readOnly: true
env:
- name: WATCH_NAMESPACE
value: "${WATCH_NAMESPACE}"
Expand All @@ -64,3 +69,7 @@ objects:
fieldPath: metadata.name
- name: OPERATOR_NAME
value: "istio-workspace"
volumes:
- name: certs
secret:
secretName: istio-workspace
179 changes: 101 additions & 78 deletions pkg/assets/isto-workspace-deploy.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pkg/client/dynclient/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dynclient

import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
coreV1 "k8s.io/api/core/v1"
rbacV1 "k8s.io/api/rbac/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
Expand Down Expand Up @@ -76,6 +77,7 @@ func (c *Client) Create(obj runtime.Object) error {
case *v1beta1.CustomResourceDefinition:
case *rbacV1.ClusterRole:
case *rbacV1.ClusterRoleBinding:
case *admissionregistrationv1.MutatingWebhookConfiguration:
default:
// For all the other types we should create resources in the desired namespace
resourceInterface = nsResourceInterface.Namespace(c.Namespace)
Expand Down
7 changes: 4 additions & 3 deletions pkg/cmd/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ func installOperator(cmd *cobra.Command, args []string) error { //nolint:gocyclo
return err
}
resources := []string{"crds/maistra.io_sessions_crd.yaml", "service_account.yaml", "cluster_role.yaml"}
templates := []string{"cluster_role_binding.yaml", "operator.tpl.yaml"}
templates := []string{"cluster_role_binding.yaml", "operator.tpl.yaml", "mutation.yaml"}

if local {
resources = []string{"crds/maistra.io_sessions_crd.yaml", "service_account.yaml", "role.yaml"}
templates = []string{"role_binding.yaml", "operator.tpl.yaml"}
templates = []string{"role_binding.yaml", "operator.tpl.yaml", "mutation.yaml"}

if envErr := os.Setenv("WATCH_NAMESPACE", namespace); envErr != nil {
return envErr
Expand Down Expand Up @@ -201,8 +201,9 @@ func (app *applier) applyTemplate(templatePath string) error {
app.c.Namespace,
name(object, templatePath),
gav.Kind))

err = app.c.Create(object)
if err != nil {
if err != nil && !errors.IsAlreadyExists(err) {
return err
}
}
Expand Down
13 changes: 12 additions & 1 deletion pkg/cmd/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/maistra/istio-workspace/pkg/log"

"github.com/spf13/cobra"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
k8sConfig "sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
Expand All @@ -30,6 +31,8 @@ var logger = func() logr.Logger {
}

var (
webhookHost = "0.0.0.0"
webhookPort = 8443
metricsHost = "0.0.0.0"
metricsPort int32 = 8383
)
Expand Down Expand Up @@ -70,7 +73,10 @@ func startOperator(cmd *cobra.Command, args []string) error {
// Create a new Cmd to provide shared dependencies and Start components
mgr, err := manager.New(cfg, manager.Options{
Namespace: namespace,
Port: webhookPort,
Host: webhookHost,
MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort),
CertDir: "/tmp/certs/",
})
if err != nil {
logger().Error(err, "")
Expand All @@ -85,6 +91,11 @@ func startOperator(cmd *cobra.Command, args []string) error {
return nil
}

if err = admissionv1beta1.AddToScheme(mgr.GetScheme()); err != nil {
logger().Error(err, "")
return nil
}

// Setup all Controllers
if err = controller.AddToManager(mgr); err != nil {
logger().Error(err, "")
Expand All @@ -95,7 +106,7 @@ func startOperator(cmd *cobra.Command, args []string) error {
hookServer := mgr.GetWebhookServer()

logger().Info("Registering webhooks to the webhook server.")
hookServer.Register("/deployment-mutation", &webhook.Admission{Handler: mutation.Webhook{Client: mgr.GetClient()}})
hookServer.Register("/deployment-mutation", &webhook.Admission{Handler: &mutation.Webhook{Client: mgr.GetClient()}})

// Create Service object to expose the metrics port.
servicePorts := []v1.ServicePort{
Expand Down
116 changes: 105 additions & 11 deletions pkg/k8s/mutation/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,65 @@ import (
"net/http"
"time"

"github.com/maistra/istio-workspace/pkg/internal/session"
"github.com/maistra/istio-workspace/pkg/model"
"github.com/go-logr/logr"

istiov1alpha1 "github.com/maistra/istio-workspace/pkg/apis/maistra/v1alpha1"
"github.com/maistra/istio-workspace/pkg/internal/session"
"github.com/maistra/istio-workspace/pkg/log"
"github.com/maistra/istio-workspace/pkg/model"

admissionv1beta1 "k8s.io/api/admission/v1beta1"
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/webhook/admission"
)

var (
logger = func() logr.Logger {
return log.Log.WithValues("type", "controller")
}
)

type data struct {
Object appsv1.Deployment
}

func (d *data) IsIkeable() bool {
return d.Object.Labels["ike.target"] != ""
//return d.Object.Labels["ike.target"] != ""
return d.Object.Spec.Selector.MatchLabels["deployment"] == "workspace"
}

func (d *data) Target() string {
return d.Object.Labels["ike.target"]
t := d.Object.Labels["ike.target"]
if t != "" {
return t
}
return "preference-v1"
}

func (d *data) Session() string {
return d.Object.Labels["ike.session"]
s := d.Object.Labels["ike.session"]
if s != "" {
return s
}
//return strings.ReplaceAll(d.Object.Name, ".", "-")
return "test"
}

func (d *data) Namespace() string {
return d.Object.Namespace
}

func (d *data) Route() string {
r := d.Object.Labels["ike.route"]
if r != "" {
return r
}
return "header:ike-session-id=feature-y"
}

// Webhook to mutate Deployments with ike.target annotations to setup routing to existing pods.
type Webhook struct {
Client client.Client
Expand All @@ -48,10 +76,24 @@ var _ admission.Handler = &Webhook{}

// Handle will create a Session with a "existing" strategy to setup a route to the upcoming deployment.
// The deployment will be injected the correct labels to get the prod route.
func (w Webhook) Handle(ctx context.Context, req admission.Request) admission.Response {
func (w *Webhook) Handle(ctx context.Context, req admission.Request) admission.Response {
// if review.Request.DryRun don't do stuff with sideeffects....

deployment := &appsv1.Deployment{}

if req.Operation == admissionv1beta1.Delete {
w.decoder.DecodeRaw(req.OldObject, deployment)
d := data{Object: *deployment}
if d.IsIkeable() {
logger().Info("Removing session", "deployment", req.Name)
err := removeSession(d)
if err != nil {
logger().Error(err, "problems removing session", "deployment", deployment.Name)
}
}
return admission.Allowed("") // TODO: impl delete behavior
}

err := w.decoder.Decode(req, deployment)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
Expand All @@ -62,21 +104,36 @@ func (w Webhook) Handle(ctx context.Context, req admission.Request) admission.Re
return admission.Allowed("not ikable, move on")
}

sess, err := createSessionAndWait(d)
logger().Info("Creating session", "deployment", req.Name)
refStatus, err := createSessionAndWait(d)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

lables := findLables(sess)
logger().Info("Session created", "deployment", req.Name)

lables := findLables(refStatus)
for k, v := range lables {
deployment.Labels[k] = v
logger().Info("Label added", "deployemnt", req.Name, k, v)
deployment.Spec.Template.Labels[k] = v
}

targetHost := findGwHost(refStatus)
if targetHost != "" {
for i := 0; i < len(deployment.Spec.Template.Spec.Containers); i++ {
c := deployment.Spec.Template.Spec.Containers[i]
logger().Info("Env added", "deployemnt", req.Name, "container", c.Name, "IKE_HOST", targetHost)
c.Env = append(c.Env, corev1.EnvVar{Name: "IKE_HOST", Value: targetHost})
deployment.Spec.Template.Spec.Containers[i] = c
}
}

marshaledDeployment, err := json.Marshal(deployment)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

logger().Info("Patch response sent", "deployemnt", req.Name)
return admission.PatchResponseFromRaw(req.Object.Raw, marshaledDeployment)
}

Expand All @@ -93,9 +150,10 @@ func createSessionAndWait(d data) (*istiov1alpha1.RefStatus, error) {
SessionName: d.Session(),
NamespaceName: d.Namespace(),
Strategy: model.StrategyExisting,
RouteExp: d.Route(),
Duration: &checkDuration,
WaitCondition: func(ref *istiov1alpha1.RefResource) bool {
return false
return true // TODO: valid wait
},
}

Expand All @@ -113,7 +171,43 @@ func createSessionAndWait(d data) (*istiov1alpha1.RefStatus, error) {
return &state.RefStatus, nil
}

func removeSession(d data) error {
options := session.Options{
DeploymentName: d.Target(),
SessionName: d.Session(),
NamespaceName: d.Namespace(),
}

c, err := session.DefaultClient(options.NamespaceName)
if err != nil {
return err
}

_, remove, err := session.RemoveHandler(options, c)
if err != nil {
return err
}
remove()

return nil
}

func findLables(ref *istiov1alpha1.RefStatus) map[string]string {
// for each Resource, if Deployment, Make label patch
for _, target := range ref.Targets {
if *target.Kind == "Deployment" || *target.Kind == "DeploymentConfig" {
lables := target.Labels
lables["version"] = lables["version"] + "-test" // TODO: dynamically lookup all target labels
return lables
}
}
return map[string]string{}
}

func findGwHost(ref *istiov1alpha1.RefStatus) string {
for _, target := range ref.Resources {
if *target.Kind == "Gateway" {
return target.Prop["hosts"]
}
}
return ""
}
Loading

0 comments on commit f42ee6d

Please sign in to comment.