Skip to content

Commit

Permalink
remove all usages for admissionregistration.k8s.io/v1beta1 mutating a…
Browse files Browse the repository at this point in the history
…nd validating webhook configs

Signed-off-by: Joe Lanford <joe.lanford@gmail.com>
  • Loading branch information
joelanford committed Aug 6, 2021
1 parent 884806c commit 06592e1
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 130 deletions.
39 changes: 18 additions & 21 deletions pkg/envtest/envtest_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
admissionv1 "k8s.io/api/admissionregistration/v1"
admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
Expand All @@ -48,60 +47,57 @@ var _ = BeforeSuite(func() {
}, StartTimeout)

func initializeWebhookInEnvironment() {
namespacedScopeV1Beta1 := admissionv1beta1.NamespacedScope
namespacedScopeV1 := admissionv1.NamespacedScope
failedTypeV1Beta1 := admissionv1beta1.Fail
failedTypeV1 := admissionv1.Fail
equivalentTypeV1Beta1 := admissionv1beta1.Equivalent
equivalentTypeV1 := admissionv1.Equivalent
noSideEffectsV1Beta1 := admissionv1beta1.SideEffectClassNone
noSideEffectsV1 := admissionv1.SideEffectClassNone
webhookPathV1 := "/failing"

env.WebhookInstallOptions = WebhookInstallOptions{
ValidatingWebhooks: []client.Object{
&admissionv1beta1.ValidatingWebhookConfiguration{
ValidatingWebhooks: []admissionv1.ValidatingWebhookConfiguration{
{
ObjectMeta: metav1.ObjectMeta{
Name: "deployment-validation-webhook-config",
},
TypeMeta: metav1.TypeMeta{
Kind: "ValidatingWebhookConfiguration",
APIVersion: "admissionregistration.k8s.io/v1beta1",
APIVersion: "admissionregistration.k8s.io/v1",
},
Webhooks: []admissionv1beta1.ValidatingWebhook{
Webhooks: []admissionv1.ValidatingWebhook{
{
Name: "deployment-validation.kubebuilder.io",
Rules: []admissionv1beta1.RuleWithOperations{
Rules: []admissionv1.RuleWithOperations{
{
Operations: []admissionv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionv1beta1.Rule{
Operations: []admissionv1.OperationType{"CREATE", "UPDATE"},
Rule: admissionv1.Rule{
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
Resources: []string{"deployments"},
Scope: &namespacedScopeV1Beta1,
Scope: &namespacedScopeV1,
},
},
},
FailurePolicy: &failedTypeV1Beta1,
MatchPolicy: &equivalentTypeV1Beta1,
SideEffects: &noSideEffectsV1Beta1,
ClientConfig: admissionv1beta1.WebhookClientConfig{
Service: &admissionv1beta1.ServiceReference{
FailurePolicy: &failedTypeV1,
MatchPolicy: &equivalentTypeV1,
SideEffects: &noSideEffectsV1,
ClientConfig: admissionv1.WebhookClientConfig{
Service: &admissionv1.ServiceReference{
Name: "deployment-validation-service",
Namespace: "default",
Path: &webhookPathV1,
},
},
AdmissionReviewVersions: []string{"v1"},
},
},
},
&admissionv1.ValidatingWebhookConfiguration{
{
ObjectMeta: metav1.ObjectMeta{
Name: "deployment-validation-webhook-config",
},
TypeMeta: metav1.TypeMeta{
Kind: "ValidatingWebhookConfiguration",
APIVersion: "admissionregistration.k8s.io/v1beta1",
APIVersion: "admissionregistration.k8s.io/v1",
},
Webhooks: []admissionv1.ValidatingWebhook{
{
Expand All @@ -127,6 +123,7 @@ func initializeWebhookInEnvironment() {
Path: &webhookPathV1,
},
},
AdmissionReviewVersions: []string{"v1"},
},
},
},
Expand Down
8 changes: 4 additions & 4 deletions pkg/envtest/testdata/webhooks/manifests.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
apiVersion: admissionregistration.k8s.io/v1beta1
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
creationTimestamp: null
Expand All @@ -10,7 +10,7 @@ webhooks:
service:
name: webhook-service
namespace: system
path: /mutate-v1beta1
path: /mutate-v1
failurePolicy: Fail
name: mpods.kb.io
rules:
Expand Down Expand Up @@ -49,7 +49,7 @@ webhooks:
resources:
- pods
---
apiVersion: admissionregistration.k8s.io/v1beta1
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
creationTimestamp: null
Expand All @@ -60,7 +60,7 @@ webhooks:
service:
name: webhook-service
namespace: system
path: /validate-v1beta1
path: /validate-v1
failurePolicy: Fail
name: vpods.kb.io
rules:
Expand Down
128 changes: 46 additions & 82 deletions pkg/envtest/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,26 @@ package envtest

import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"time"

admissionv1 "k8s.io/api/admissionregistration/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/rest"
"sigs.k8s.io/yaml"

"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/internal/testing/addr"
"sigs.k8s.io/controller-runtime/pkg/internal/testing/certs"
"sigs.k8s.io/yaml"
)

// WebhookInstallOptions are the options for installing mutating or validating webhooks.
Expand All @@ -43,10 +43,10 @@ type WebhookInstallOptions struct {
Paths []string

// MutatingWebhooks is a list of MutatingWebhookConfigurations to install
MutatingWebhooks []client.Object
MutatingWebhooks []admissionv1.MutatingWebhookConfiguration

// ValidatingWebhooks is a list of ValidatingWebhookConfigurations to install
ValidatingWebhooks []client.Object
ValidatingWebhooks []admissionv1.ValidatingWebhookConfiguration

// IgnoreErrorIfPathMissing will ignore an error if a DirectoryPath does not exist when set to true
IgnoreErrorIfPathMissing bool
Expand Down Expand Up @@ -88,57 +88,27 @@ func (o *WebhookInstallOptions) ModifyWebhookDefinitions() error {
return err
}

for i, unstructuredHook := range runtimeListToUnstructured(o.MutatingWebhooks) {
webhooks, found, err := unstructured.NestedSlice(unstructuredHook.Object, "webhooks")
if !found || err != nil {
return fmt.Errorf("unexpected object, %v", err)
}
for j := range webhooks {
webhook, err := modifyWebhook(webhooks[j].(map[string]interface{}), caData, hostPort)
if err != nil {
return err
}
webhooks[j] = webhook
unstructuredHook.Object["webhooks"] = webhooks
o.MutatingWebhooks[i] = unstructuredHook
for i := range o.MutatingWebhooks {
for j := range o.MutatingWebhooks[i].Webhooks {
updateClientConfig(&o.MutatingWebhooks[i].Webhooks[j].ClientConfig, hostPort, caData)
}
}

for i, unstructuredHook := range runtimeListToUnstructured(o.ValidatingWebhooks) {
webhooks, found, err := unstructured.NestedSlice(unstructuredHook.Object, "webhooks")
if !found || err != nil {
return fmt.Errorf("unexpected object, %v", err)
}
for j := range webhooks {
webhook, err := modifyWebhook(webhooks[j].(map[string]interface{}), caData, hostPort)
if err != nil {
return err
}
webhooks[j] = webhook
unstructuredHook.Object["webhooks"] = webhooks
o.ValidatingWebhooks[i] = unstructuredHook
for i := range o.ValidatingWebhooks {
for j := range o.ValidatingWebhooks[i].Webhooks {
updateClientConfig(&o.ValidatingWebhooks[i].Webhooks[j].ClientConfig, hostPort, caData)
}
}
return nil
}

func modifyWebhook(webhook map[string]interface{}, caData []byte, hostPort string) (map[string]interface{}, error) {
clientConfig, found, err := unstructured.NestedMap(webhook, "clientConfig")
if !found || err != nil {
return nil, fmt.Errorf("cannot find clientconfig: %v", err)
}
clientConfig["caBundle"] = base64.StdEncoding.EncodeToString(caData)
servicePath, found, err := unstructured.NestedString(clientConfig, "service", "path")
if found && err == nil {
// we cannot use service in integration tests since we're running controller outside cluster
// the intent here is that we swap out service for raw address because we don't have an actually standard kube service network.
// We want to users to be able to use your standard config though
url := fmt.Sprintf("https://%s/%s", hostPort, servicePath)
clientConfig["url"] = url
clientConfig["service"] = nil
func updateClientConfig(cc *admissionv1.WebhookClientConfig, hostPort string, caData []byte) {
cc.CABundle = caData
if cc.Service != nil && cc.Service.Path != nil {
url := fmt.Sprintf("https://%s/%s", hostPort, *cc.Service.Path)
cc.URL = &url
cc.Service = nil
}
webhook["clientConfig"] = clientConfig
return webhook, nil
}

func (o *WebhookInstallOptions) generateHostPort() (string, error) {
Expand Down Expand Up @@ -199,12 +169,19 @@ func (o *WebhookInstallOptions) Cleanup() error {

// WaitForWebhooks waits for the Webhooks to be available through API server.
func WaitForWebhooks(config *rest.Config,
mutatingWebhooks []client.Object,
validatingWebhooks []client.Object,
mutatingWebhooks []admissionv1.MutatingWebhookConfiguration,
validatingWebhooks []admissionv1.ValidatingWebhookConfiguration,
options WebhookInstallOptions) error {
waitingFor := map[schema.GroupVersionKind]*sets.String{}

for _, hook := range runtimeListToUnstructured(append(validatingWebhooks, mutatingWebhooks...)) {
for _, hook := range mutatingWebhooks {
if _, ok := waitingFor[hook.GroupVersionKind()]; !ok {
waitingFor[hook.GroupVersionKind()] = &sets.String{}
}
waitingFor[hook.GroupVersionKind()].Insert(hook.GetName())
}

for _, hook := range validatingWebhooks {
if _, ok := waitingFor[hook.GroupVersionKind()]; !ok {
waitingFor[hook.GroupVersionKind()] = &sets.String{}
}
Expand Down Expand Up @@ -297,31 +274,33 @@ func (o *WebhookInstallOptions) setupCA() error {
return err
}

func createWebhooks(config *rest.Config, mutHooks []client.Object, valHooks []client.Object) error {
func createWebhooks(config *rest.Config, mutHooks []admissionv1.MutatingWebhookConfiguration, valHooks []admissionv1.ValidatingWebhookConfiguration) error {
cs, err := client.New(config, client.Options{})
if err != nil {
return err
}

// Create each webhook
for _, hook := range runtimeListToUnstructured(mutHooks) {
for _, hook := range mutHooks {
hook := hook
log.V(1).Info("installing mutating webhook", "webhook", hook.GetName())
if err := ensureCreated(cs, hook); err != nil {
if err := ensureCreated(cs, &hook); err != nil {
return err
}
}
for _, hook := range runtimeListToUnstructured(valHooks) {
for _, hook := range valHooks {
hook := hook
log.V(1).Info("installing validating webhook", "webhook", hook.GetName())
if err := ensureCreated(cs, hook); err != nil {
if err := ensureCreated(cs, &hook); err != nil {
return err
}
}
return nil
}

// ensureCreated creates or update object if already exists in the cluster.
func ensureCreated(cs client.Client, obj *unstructured.Unstructured) error {
existing := obj.DeepCopy()
func ensureCreated(cs client.Client, obj client.Object) error {
existing := obj.DeepCopyObject().(client.Object)
err := cs.Get(context.Background(), client.ObjectKey{Name: obj.GetName()}, existing)
switch {
case apierrors.IsNotFound(err):
Expand Down Expand Up @@ -364,7 +343,7 @@ func parseWebhook(options *WebhookInstallOptions) error {

// readWebhooks reads the Webhooks from files and Unmarshals them into structs
// returns slice of mutating and validating webhook configurations.
func readWebhooks(path string) ([]client.Object, []client.Object, error) {
func readWebhooks(path string) ([]admissionv1.MutatingWebhookConfiguration, []admissionv1.ValidatingWebhookConfiguration, error) {
// Get the webhook files
var files []os.FileInfo
var err error
Expand All @@ -382,8 +361,8 @@ func readWebhooks(path string) ([]client.Object, []client.Object, error) {
// file extensions that may contain Webhooks
resourceExtensions := sets.NewString(".json", ".yaml", ".yml")

var mutHooks []client.Object
var valHooks []client.Object
var mutHooks []admissionv1.MutatingWebhookConfiguration
var valHooks []admissionv1.ValidatingWebhookConfiguration
for _, file := range files {
// Only parse allowlisted file types
if !resourceExtensions.Has(filepath.Ext(file.Name())) {
Expand All @@ -403,24 +382,23 @@ func readWebhooks(path string) ([]client.Object, []client.Object, error) {
}

const (
admissionregv1 = "admissionregistration.k8s.io/v1"
admissionregv1beta1 = "admissionregistration.k8s.io/v1beta1"
admissionregv1 = "admissionregistration.k8s.io/v1"
)
switch {
case generic.Kind == "MutatingWebhookConfiguration":
if generic.APIVersion != admissionregv1beta1 && generic.APIVersion != admissionregv1 {
return nil, nil, fmt.Errorf("only v1beta1 and v1 are supported right now for MutatingWebhookConfiguration (name: %s)", generic.Name)
if generic.APIVersion != admissionregv1 {
return nil, nil, fmt.Errorf("only v1 is supported right now for MutatingWebhookConfiguration (name: %s)", generic.Name)
}
hook := &unstructured.Unstructured{}
hook := admissionv1.MutatingWebhookConfiguration{}
if err := yaml.Unmarshal(doc, &hook); err != nil {
return nil, nil, err
}
mutHooks = append(mutHooks, hook)
case generic.Kind == "ValidatingWebhookConfiguration":
if generic.APIVersion != admissionregv1beta1 && generic.APIVersion != admissionregv1 {
return nil, nil, fmt.Errorf("only v1beta1 and v1 are supported right now for ValidatingWebhookConfiguration (name: %s)", generic.Name)
if generic.APIVersion != admissionregv1 {
return nil, nil, fmt.Errorf("only v1 is supported right now for ValidatingWebhookConfiguration (name: %s)", generic.Name)
}
hook := &unstructured.Unstructured{}
hook := admissionv1.ValidatingWebhookConfiguration{}
if err := yaml.Unmarshal(doc, &hook); err != nil {
return nil, nil, err
}
Expand All @@ -434,17 +412,3 @@ func readWebhooks(path string) ([]client.Object, []client.Object, error) {
}
return mutHooks, valHooks, nil
}

func runtimeListToUnstructured(l []client.Object) []*unstructured.Unstructured {
res := []*unstructured.Unstructured{}
for _, obj := range l {
m, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj.DeepCopyObject())
if err != nil {
continue
}
res = append(res, &unstructured.Unstructured{
Object: m,
})
}
return res
}
Loading

0 comments on commit 06592e1

Please sign in to comment.