pkg/webhook/psbinding/table_test.go

1895 lines
50 KiB
Go

/*
Copyright 2019 The Knative Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package psbinding
import (
"context"
"encoding/json"
"fmt"
"testing"
"time"
"github.com/mattbaird/jsonpatch"
"knative.dev/pkg/apis"
"knative.dev/pkg/client/injection/ducks/duck/v1/podspecable"
kubeclient "knative.dev/pkg/client/injection/kube/client/fake"
_ "knative.dev/pkg/client/injection/kube/informers/admissionregistration/v1beta1/mutatingwebhookconfiguration/fake"
_ "knative.dev/pkg/client/injection/kube/informers/core/v1/secret/fake"
dynamicclient "knative.dev/pkg/injection/clients/dynamicclient/fake"
"knative.dev/pkg/tracker"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrs "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"
clientgotesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"knative.dev/pkg/apis/duck"
duckv1 "knative.dev/pkg/apis/duck/v1"
duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1"
"knative.dev/pkg/configmap"
"knative.dev/pkg/controller"
"knative.dev/pkg/ptr"
"knative.dev/pkg/system"
"knative.dev/pkg/webhook"
certresources "knative.dev/pkg/webhook/certificates/resources"
. "knative.dev/pkg/reconciler/testing"
. "knative.dev/pkg/testing/duck"
. "knative.dev/pkg/webhook/testing"
)
func checkDeploymentIsPatched(t *testing.T, r *TableRow) {
t.Helper()
ac := r.Reconciler.(webhook.AdmissionController)
d := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
Labels: map[string]string{
"foo": "bar",
},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
}},
},
},
},
}
b, err := json.Marshal(d)
if err != nil {
t.Fatalf("Unable to serialize deployment: %v", err)
}
req := &admissionv1beta1.AdmissionRequest{
Operation: admissionv1beta1.Create,
Kind: metav1.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
Namespace: d.Namespace,
Object: runtime.RawExtension{Raw: b},
}
// It is allowed, and patched to include the environment variable.
resp := ac.Admit(r.Ctx, req)
ExpectAllowed(t, resp)
ExpectPatches(t, resp.Patch, []jsonpatch.JsonPatchOperation{{
Operation: "add",
Path: "/spec/template/spec/containers/0/env",
Value: []interface{}{map[string]interface{}{
"name": "FOO",
"value": "the-value",
}},
}})
}
func checkDeploymentIsPatchedBack(t *testing.T, r *TableRow) {
t.Helper()
ac := r.Reconciler.(webhook.AdmissionController)
d := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
Labels: map[string]string{
"foo": "bar",
},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
Env: []corev1.EnvVar{{
Name: "FOO",
Value: "the-value",
}},
}},
},
},
},
}
b, err := json.Marshal(d)
if err != nil {
t.Fatalf("Unable to serialize deployment: %v", err)
}
req := &admissionv1beta1.AdmissionRequest{
Operation: admissionv1beta1.Create,
Kind: metav1.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
Namespace: d.Namespace,
Object: runtime.RawExtension{Raw: b},
}
// It is allowed, and patched to REMOVE the environment variable.
resp := ac.Admit(r.Ctx, req)
ExpectAllowed(t, resp)
ExpectPatches(t, resp.Patch, []jsonpatch.JsonPatchOperation{{
Operation: "remove",
Path: "/spec/template/spec/containers/0/env",
}})
}
func checkDeploymentIsNotPatched(t *testing.T, r *TableRow) {
t.Helper()
ac := r.Reconciler.(webhook.AdmissionController)
d := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "off-it",
Labels: map[string]string{
"foo": "baz",
},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
}},
},
},
},
}
b, err := json.Marshal(d)
if err != nil {
t.Fatalf("Unable to serialize deployment: %v", err)
}
req := &admissionv1beta1.AdmissionRequest{
Operation: admissionv1beta1.Create,
Kind: metav1.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
Namespace: d.Namespace,
Object: runtime.RawExtension{Raw: b},
}
// It is allowed, but not patched.
resp := ac.Admit(r.Ctx, req)
ExpectAllowed(t, resp)
if want, got := "", string(resp.Patch); want != got {
t.Errorf("Admit() = %s, got %s", got, want)
}
}
func checkDeleteIgnored(t *testing.T, r *TableRow) {
t.Helper()
ac := r.Reconciler.(webhook.AdmissionController)
d := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
Labels: map[string]string{
"foo": "bar",
},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
}},
},
},
},
}
b, err := json.Marshal(d)
if err != nil {
t.Fatalf("Unable to serialize deployment: %v", err)
}
req := &admissionv1beta1.AdmissionRequest{
Operation: admissionv1beta1.Delete,
Kind: metav1.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
Namespace: d.Namespace,
Object: runtime.RawExtension{Raw: b},
}
// It is allowed, and patched to include the environment variable.
resp := ac.Admit(r.Ctx, req)
ExpectAllowed(t, resp)
if want, got := "", string(resp.Patch); want != got {
t.Errorf("Admit() = %s, got %s", got, want)
}
}
func TestWebhookReconcile(t *testing.T) {
name, path := "foo.bar.baz", "/blah"
secretName := "webhook-secret"
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: system.Namespace(),
},
Data: map[string][]byte{
certresources.ServerKey: []byte("present"),
certresources.ServerCert: []byte("present"),
certresources.CACert: []byte("present"),
},
}
equivalent := admissionregistrationv1beta1.Equivalent
// The key to use, which for this singleton reconciler doesn't matter (although the
// namespace matters for namespace validation).
key := system.Namespace() + "/does not matter"
table := TableTest{{
Name: "no secret",
Key: key,
WantErr: true,
}, {
Name: "secret missing CA Cert",
Key: key,
Objects: []runtime.Object{&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: system.Namespace(),
},
Data: map[string][]byte{
certresources.ServerKey: []byte("present"),
certresources.ServerCert: []byte("present"),
// certresources.CACert: []byte("missing"),
},
}},
WantErr: true,
}, {
Name: "secret exists, but MWH does not",
Key: key,
Objects: []runtime.Object{secret},
WantErr: true,
}, {
Name: "secret and MWH exist, missing service reference",
Key: key,
Objects: []runtime.Object{secret,
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
}},
},
},
WantErr: true,
}, {
Name: "secret and MWH exist, missing other stuff",
Key: key,
Objects: []runtime.Object{secret,
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
},
},
}},
},
},
WantUpdates: []clientgotesting.UpdateActionImpl{{
Object: &admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
// Path is added.
Path: ptr.String(path),
},
// CABundle is added.
CABundle: []byte("present"),
},
// Rules are added.
Rules: nil,
// MatchPolicy is added.
MatchPolicy: &equivalent,
// Selectors are added.
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
}},
}, {
Name: "secret and MWH exist, added fields are incorrect",
Key: key,
Objects: []runtime.Object{secret,
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
// Incorrect
Path: ptr.String("incorrect"),
},
// Incorrect
CABundle: []byte("incorrect"),
},
// Incorrect (really just incomplete)
Rules: []admissionregistrationv1beta1.RuleWithOperations{{
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"pkg.knative.dev"},
APIVersions: []string{"v1alpha1"},
Resources: []string{"innerdefaultresources/*"},
},
}},
}},
},
},
WantUpdates: []clientgotesting.UpdateActionImpl{{
Object: &admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
// Path is fixed.
Path: ptr.String(path),
},
// CABundle is fixed.
CABundle: []byte("present"),
},
// Rules are fixed.
Rules: nil,
// MatchPolicy is added.
MatchPolicy: &equivalent,
// Selectors are added.
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
}},
}, {
Name: "failure updating MWH",
Key: key,
WantErr: true,
WithReactors: []clientgotesting.ReactionFunc{
InduceFailure("update", "mutatingwebhookconfigurations"),
},
Objects: []runtime.Object{secret,
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
// Incorrect
Path: ptr.String("incorrect"),
},
// Incorrect
CABundle: []byte("incorrect"),
},
// Incorrect (really just incomplete)
Rules: []admissionregistrationv1beta1.RuleWithOperations{{
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"pkg.knative.dev"},
APIVersions: []string{"v1alpha1"},
Resources: []string{"innerdefaultresources/*"},
},
}},
}},
},
},
WantUpdates: []clientgotesting.UpdateActionImpl{{
Object: &admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
// Path is fixed.
Path: ptr.String(path),
},
// CABundle is fixed.
CABundle: []byte("present"),
},
// Rules are fixed.
Rules: nil,
// MatchPolicy is added.
MatchPolicy: &equivalent,
// Selectors are added.
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
}},
}, {
Name: ":fire: everything is fine :fire:",
Key: key,
Objects: []runtime.Object{secret,
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
// Path is fine.
Path: ptr.String(path),
},
// CABundle is fine.
CABundle: []byte("present"),
},
// Rules are fine.
Rules: nil,
// MatchPolicy is fine.
MatchPolicy: &equivalent,
// Selectors are fine.
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
},
}, {
Name: ":fire: everything is fine, using opt-out (inclusion) :fire:",
Key: key,
Ctx: WithOptOutSelector(context.Background()),
Objects: []runtime.Object{secret,
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
// Path is fine.
Path: ptr.String(path),
},
// CABundle is fine.
CABundle: []byte("present"),
},
// Rules are fine.
Rules: nil,
// MatchPolicy is fine.
MatchPolicy: &equivalent,
// Selectors are fine.
NamespaceSelector: &InclusionSelector,
ObjectSelector: &InclusionSelector,
}},
},
},
}, {
Name: "a new binding has entered the match",
Key: key,
Objects: []runtime.Object{secret,
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "random.knative.dev/v2beta3",
Kind: "Knoodle",
Namespace: "foo",
Name: "on-it",
},
},
},
},
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
// Path is fine.
Path: ptr.String(path),
},
// CABundle is fine.
CABundle: []byte("present"),
},
// Rules are fine.
Rules: nil,
// MatchPolicy is fine.
MatchPolicy: &equivalent,
// Selectors are fine.
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
},
WantUpdates: []clientgotesting.UpdateActionImpl{{
Object: &admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
Path: ptr.String(path),
},
CABundle: []byte("present"),
},
// A new rule is added to intercept the new type.
Rules: []admissionregistrationv1beta1.RuleWithOperations{{
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"random.knative.dev"},
APIVersions: []string{"v2beta3"},
Resources: []string{"knoodles/*"},
},
}},
MatchPolicy: &equivalent,
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
}},
}, {
Name: "steady state direct bindings",
Key: key,
Objects: []runtime.Object{secret,
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar1",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "random.knative.dev/v2beta3",
Kind: "Knoodle",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "one-value",
},
},
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar2",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "the-value",
},
},
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
Path: ptr.String(path),
},
CABundle: []byte("present"),
},
// A new rule is added to intercept the new type.
Rules: []admissionregistrationv1beta1.RuleWithOperations{{
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
Resources: []string{"deployments/*"},
},
}, {
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"random.knative.dev"},
APIVersions: []string{"v2beta3"},
Resources: []string{"knoodles/*"},
},
}},
MatchPolicy: &equivalent,
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
},
// Verify that Admit properly patches deployments after being programmed
// with the binding.
PostConditions: []func(*testing.T, *TableRow){
checkDeploymentIsPatched,
checkDeploymentIsNotPatched,
checkDeleteIgnored,
},
}, {
Name: "steady state selector",
Key: key,
Objects: []runtime.Object{secret,
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar1",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "random.knative.dev/v2beta3",
Kind: "Knoodle",
Namespace: "foo",
Selector: &metav1.LabelSelector{
// Match everything.
MatchLabels: map[string]string{},
},
},
},
Foo: "one-value",
},
},
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar2",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
Foo: "the-value",
},
},
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
Path: ptr.String(path),
},
CABundle: []byte("present"),
},
// A new rule is added to intercept the new type.
Rules: []admissionregistrationv1beta1.RuleWithOperations{{
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
Resources: []string{"deployments/*"},
},
}, {
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"random.knative.dev"},
APIVersions: []string{"v2beta3"},
Resources: []string{"knoodles/*"},
},
}},
MatchPolicy: &equivalent,
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
},
// Verify that Admit properly patches deployments after being programmed
// with the binding.
PostConditions: []func(*testing.T, *TableRow){
checkDeploymentIsPatched,
checkDeploymentIsNotPatched,
checkDeleteIgnored,
},
}, {
Name: "tombstoned binding undoes patch",
Key: key,
Objects: []runtime.Object{secret,
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar2",
DeletionTimestamp: &metav1.Time{time.Now()},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
Foo: "the-value",
},
},
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
Path: ptr.String(path),
},
CABundle: []byte("present"),
},
Rules: []admissionregistrationv1beta1.RuleWithOperations{{
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
Resources: []string{"deployments/*"},
},
}},
MatchPolicy: &equivalent,
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
},
// Verify that Admit properly patches deployments after being programmed
// with the binding.
PostConditions: []func(*testing.T, *TableRow){
checkDeploymentIsPatchedBack,
checkDeploymentIsNotPatched,
checkDeleteIgnored,
},
}, {
Name: "multiple new bindings have entered the match",
Key: key,
Objects: []runtime.Object{secret,
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "random.knative.dev/v2beta3",
Kind: "Knoodle",
Namespace: "foo",
Name: "on-it",
},
},
},
},
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "blah",
Name: "bazinga",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "pseudorandom.knative.dev/v3beta1",
Kind: "Knoogle",
Namespace: "blah",
Name: "oh-yeah",
},
},
},
},
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
// Path is fine.
Path: ptr.String(path),
},
// CABundle is fine.
CABundle: []byte("present"),
},
// Rules are fine.
Rules: nil,
// MatchPolicy is fine.
MatchPolicy: &equivalent,
// Selectors are fine.
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
},
WantUpdates: []clientgotesting.UpdateActionImpl{{
Object: &admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
Path: ptr.String(path),
},
CABundle: []byte("present"),
},
// New rules are added to intercept the new types.
Rules: []admissionregistrationv1beta1.RuleWithOperations{{
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"pseudorandom.knative.dev"},
APIVersions: []string{"v3beta1"},
Resources: []string{"knoogles/*"},
},
}, {
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"random.knative.dev"},
APIVersions: []string{"v2beta3"},
Resources: []string{"knoodles/*"},
},
}},
MatchPolicy: &equivalent,
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
}},
}, {
Name: "a new selector-based binding has entered the match",
Key: key,
Objects: []runtime.Object{secret,
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "random.knative.dev/v2beta3",
Kind: "Knoodle",
Namespace: "foo",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
},
},
},
},
&admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
// Path is fine.
Path: ptr.String(path),
},
// CABundle is fine.
CABundle: []byte("present"),
},
// Rules are fine.
Rules: nil,
// MatchPolicy is fine.
MatchPolicy: &equivalent,
// Selectors are fine.
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
},
WantUpdates: []clientgotesting.UpdateActionImpl{{
Object: &admissionregistrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1beta1.MutatingWebhook{{
Name: name,
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Namespace: system.Namespace(),
Name: "webhook",
Path: ptr.String(path),
},
CABundle: []byte("present"),
},
// A new rule is added to intercept the new type.
Rules: []admissionregistrationv1beta1.RuleWithOperations{{
Operations: []admissionregistrationv1beta1.OperationType{"CREATE", "UPDATE"},
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"random.knative.dev"},
APIVersions: []string{"v2beta3"},
Resources: []string{"knoodles/*"},
},
}},
MatchPolicy: &equivalent,
NamespaceSelector: &ExclusionSelector,
ObjectSelector: &ExclusionSelector,
}},
},
}},
}}
table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler {
return &Reconciler{
Name: name,
HandlerPath: path,
SecretName: secretName,
Client: kubeclient.Get(ctx),
MWHLister: listers.GetMutatingWebhookConfigurationLister(),
SecretLister: listers.GetSecretLister(),
ListAll: func() ([]Bindable, error) {
bl := make([]Bindable, 0)
for _, elt := range listers.GetDuckObjects() {
b, ok := elt.(Bindable)
if !ok {
continue
}
bl = append(bl, b)
}
return bl, nil
},
}
}))
}
func TestNew(t *testing.T) {
ctx, _ := SetupFakeContext(t)
ctx = webhook.WithOptions(ctx, webhook.Options{})
c := NewAdmissionController(ctx, "foo", "/bar",
func(context.Context, cache.ResourceEventHandler) ListAll {
return func() ([]Bindable, error) {
return nil, nil
}
},
func(ctx context.Context, b Bindable) (context.Context, error) {
return ctx, nil
})
if c == nil {
t.Fatal("Expected NewController to return a non-nil value")
}
}
func TestBaseReconcile(t *testing.T) {
table := TableTest{{
Name: "bad key",
Key: "this/is/a/bad/key",
}, {
Name: "not found",
Key: "its/missing",
}, {
Name: "add finalizer, add env var",
Key: "foo/bar",
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "asdfasdfasdfasdf",
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
}},
},
},
},
},
},
WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
Object: mustTU(t, &TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "asdfasdfasdfasdf",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
}),
}},
WantPatches: []clientgotesting.PatchActionImpl{
patchAddFinalizer("foo", "bar", "" /* resource version */),
patchAddEnv("foo", "on-it", "asdfasdfasdfasdf"),
},
}, {
Name: "failure adding finalizer",
Key: "foo/bar",
WantErr: true,
WithReactors: []clientgotesting.ReactionFunc{
InduceFailure("patch", "testbindables"),
},
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "asdfasdfasdfasdf",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
},
},
WantPatches: []clientgotesting.PatchActionImpl{
patchAddFinalizer("foo", "bar", "" /* resource version */),
},
}, {
Name: "failure patching deployment",
Key: "foo/bar",
WantErr: true,
WithReactors: []clientgotesting.ReactionFunc{
InduceFailure("patch", "deployments"),
},
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
Finalizers: []string{"testbindables.duck.knative.dev"},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "asdfasdfasdfasdf",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
}},
},
},
},
},
},
WantPatches: []clientgotesting.PatchActionImpl{
patchAddEnv("foo", "on-it", "asdfasdfasdfasdf"),
},
WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
Object: mustTU(t, &TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
Finalizers: []string{"testbindables.duck.knative.dev"},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "asdfasdfasdfasdf",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "False",
Reason: "BindingFailed",
Message: "failed binding subject on-it: inducing failure for patch deployments",
}},
},
},
}),
}},
}, {
Name: "steady state",
Key: "foo/bar",
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
Finalizers: []string{"testbindables.duck.knative.dev"},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "asdfasdfasdfasdf",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
Env: []corev1.EnvVar{{
Name: "FOO",
Value: "asdfasdfasdfasdf",
}},
}},
},
},
},
},
},
}, {
Name: "finalizing, but not our turn.",
Key: "foo/bar",
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
DeletionTimestamp: &metav1.Time{time.Now()},
Finalizers: []string{
"slow.your.role",
"testbindables.duck.knative.dev",
},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "new value",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
},
},
}, {
Name: "finalizing, missing subject (remove the finalizer).",
Key: "foo/bar",
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
DeletionTimestamp: &metav1.Time{time.Now()},
Finalizers: []string{
"testbindables.duck.knative.dev",
},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "new value",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "False",
Reason: "SubjectMissing",
Message: `deployments.apps "on-it" not found`,
}},
},
},
},
},
WantPatches: []clientgotesting.PatchActionImpl{
patchRemoveFinalizer("foo", "bar", "" /* resource version */),
},
}, {
Name: "finalizing (unbind, and remove the finalizer)",
Key: "foo/bar",
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
DeletionTimestamp: &metav1.Time{time.Now()},
Finalizers: []string{
"testbindables.duck.knative.dev",
},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "value",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
Env: []corev1.EnvVar{{
Name: "FOO",
Value: "value",
}},
}},
},
},
},
},
},
WantPatches: []clientgotesting.PatchActionImpl{
patchRemoveEnv("foo", "on-it"),
patchRemoveFinalizer("foo", "bar", "" /* resource version */),
},
}, {
Name: "add finalizer, add env var (via selector)",
Key: "foo/bar",
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{},
},
},
},
Foo: "asdfasdfasdfasdf",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
}},
},
},
},
},
},
WantPatches: []clientgotesting.PatchActionImpl{
patchAddFinalizer("foo", "bar", "" /* resource version */),
patchAddEnv("foo", "on-it", "asdfasdfasdfasdf"),
},
}, {
Name: "steady state (via selector)",
Key: "foo/bar",
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
Finalizers: []string{"testbindables.duck.knative.dev"},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{},
},
},
},
Foo: "asdfasdfasdfasdf",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
Env: []corev1.EnvVar{{
Name: "FOO",
Value: "asdfasdfasdfasdf",
}},
}},
},
},
},
},
},
}, {
Name: "finalizing, missing subject (remove the finalizer, via selector)",
Key: "foo/bar",
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
DeletionTimestamp: &metav1.Time{time.Now()},
Finalizers: []string{
"testbindables.duck.knative.dev",
},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{},
},
},
},
Foo: "new value",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
},
},
WantPatches: []clientgotesting.PatchActionImpl{
patchRemoveFinalizer("foo", "bar", "" /* resource version */),
},
}, {
Name: "finalizing (unbind, and remove the finalizer, via selector)",
Key: "foo/bar",
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
DeletionTimestamp: &metav1.Time{time.Now()},
Finalizers: []string{
"testbindables.duck.knative.dev",
},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{},
},
},
},
Foo: "value",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
Env: []corev1.EnvVar{{
Name: "FOO",
Value: "value",
}},
}},
},
},
},
},
},
WantPatches: []clientgotesting.PatchActionImpl{
patchRemoveEnv("foo", "on-it"),
patchRemoveFinalizer("foo", "bar", "" /* resource version */),
},
}, {
Name: "failure updating status",
Key: "foo/bar",
WantErr: true,
WithReactors: []clientgotesting.ReactionFunc{
InduceFailure("update", "testbindables"),
},
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
Finalizers: []string{"testbindables.duck.knative.dev"},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "asdfasdfasdfasdf",
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
Env: []corev1.EnvVar{{
Name: "FOO",
Value: "asdfasdfasdfasdf",
}},
}},
},
},
},
},
},
WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
Object: mustTU(t, &TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
Finalizers: []string{"testbindables.duck.knative.dev"},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Name: "on-it",
},
},
Foo: "asdfasdfasdfasdf",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "True",
}},
},
},
}),
}},
}, {
Name: "finalizing (error during unbind)",
Key: "foo/bar",
WantErr: true,
WithReactors: []clientgotesting.ReactionFunc{
InduceFailure("patch", "deployments"),
},
Objects: []runtime.Object{
&TestBindable{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "bar",
DeletionTimestamp: &metav1.Time{time.Now()},
Finalizers: []string{
"testbindables.duck.knative.dev",
},
},
Spec: TestBindableSpec{
BindingSpec: duckv1alpha1.BindingSpec{
Subject: tracker.Reference{
APIVersion: "apps/v1",
Kind: "Deployment",
Namespace: "foo",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{},
},
},
},
Foo: "value",
},
Status: TestBindableStatus{
Status: duckv1.Status{
Conditions: []apis.Condition{{
Type: "Ready",
Status: "False",
Reason: "BindingFailed",
Message: "failed binding subject on-it: inducing failure for patch deployments",
}},
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "on-it",
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "foo",
Image: "busybox",
Env: []corev1.EnvVar{{
Name: "FOO",
Value: "value",
}},
}},
},
},
},
},
},
WantPatches: []clientgotesting.PatchActionImpl{
patchRemoveEnv("foo", "on-it"),
},
}}
table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler {
gvr := SchemeGroupVersion.WithResource("testbindables")
ctx = podspecable.WithDuck(ctx)
dc := dynamicclient.Get(ctx)
return &BaseReconciler{
GVR: gvr,
DynamicClient: dc,
Factory: podspecable.Get(ctx),
Tracker: &FakeTracker{},
Recorder: record.NewFakeRecorder(20),
Get: func(namespace, name string) (Bindable, error) {
for _, elt := range listers.GetDuckObjects() {
b, ok := elt.(*TestBindable)
if !ok {
continue
}
if b.Namespace != namespace || b.Name != name {
continue
}
return b, nil
}
return nil, apierrs.NewNotFound(gvr.GroupResource(), name)
},
}
}))
}
func mustTU(t *testing.T, ro duck.OneOfOurs) *unstructured.Unstructured {
u, err := duck.ToUnstructured(ro)
if err != nil {
t.Fatalf("ToUnstructured(%+v) = %v", ro, err)
}
return u
}
func patchAddFinalizer(namespace, name, resourceVersion string) clientgotesting.PatchActionImpl {
action := clientgotesting.PatchActionImpl{}
action.Name = name
action.Namespace = namespace
patch := fmt.Sprintf(`{"metadata":{"finalizers":["testbindables.duck.knative.dev"],"resourceVersion":%q}}`, resourceVersion)
action.Patch = []byte(patch)
return action
}
func patchRemoveFinalizer(namespace, name, resourceVersion string) clientgotesting.PatchActionImpl {
action := clientgotesting.PatchActionImpl{}
action.Name = name
action.Namespace = namespace
patch := fmt.Sprintf(`{"metadata":{"finalizers":[],"resourceVersion":%q}}`, resourceVersion)
action.Patch = []byte(patch)
return action
}
func patchAddEnv(namespace, name, value string) clientgotesting.PatchActionImpl {
action := clientgotesting.PatchActionImpl{}
action.Name = name
action.Namespace = namespace
patch := fmt.Sprintf(`[{"op":"add","path":"/spec/template/spec/containers/0/env","value":[{"name":"FOO","value":%q}]}]`, value)
action.Patch = []byte(patch)
return action
}
func patchReplaceEnv(namespace, name, value string) clientgotesting.PatchActionImpl {
action := clientgotesting.PatchActionImpl{}
action.Name = name
action.Namespace = namespace
patch := fmt.Sprintf(`[{"op":"replace","path":"/spec/template/spec/containers/0/env/0/value","value":%q}]`, value)
action.Patch = []byte(patch)
return action
}
func patchRemoveEnv(namespace, name string) clientgotesting.PatchActionImpl {
action := clientgotesting.PatchActionImpl{}
action.Name = name
action.Namespace = namespace
patch := `[{"op":"remove","path":"/spec/template/spec/containers/0/env"}]`
action.Patch = []byte(patch)
return action
}