mirror of https://github.com/knative/pkg.git
2316 lines
61 KiB
Go
2316 lines
61 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"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
jsonpatch "gomodules.xyz/jsonpatch/v2"
|
|
fakek8s "k8s.io/client-go/kubernetes/fake"
|
|
"knative.dev/pkg/apis"
|
|
"knative.dev/pkg/client/injection/ducks/duck/v1/podspecable"
|
|
kubeclient "knative.dev/pkg/client/injection/kube/client/fake"
|
|
mwhinformer "knative.dev/pkg/client/injection/kube/informers/admissionregistration/v1/mutatingwebhookconfiguration"
|
|
_ "knative.dev/pkg/client/injection/kube/informers/admissionregistration/v1/mutatingwebhookconfiguration/fake"
|
|
dynamicclient "knative.dev/pkg/injection/clients/dynamicclient/fake"
|
|
secretinformer "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret"
|
|
_ "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret/fake"
|
|
pkgreconciler "knative.dev/pkg/reconciler"
|
|
"knative.dev/pkg/tracker"
|
|
|
|
admissionv1 "k8s.io/api/admission/v1"
|
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
|
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"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
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.Fatal("Unable to serialize deployment:", err)
|
|
}
|
|
|
|
req := &admissionv1.AdmissionRequest{
|
|
Operation: admissionv1.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.Fatal("Unable to serialize deployment:", err)
|
|
}
|
|
|
|
req := &admissionv1.AdmissionRequest{
|
|
Operation: admissionv1.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.Fatal("Unable to serialize deployment:", err)
|
|
}
|
|
|
|
req := &admissionv1.AdmissionRequest{
|
|
Operation: admissionv1.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.Fatal("Unable to serialize deployment:", err)
|
|
}
|
|
|
|
req := &admissionv1.AdmissionRequest{
|
|
Operation: admissionv1.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 := admissionregistrationv1.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,
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
}},
|
|
},
|
|
},
|
|
WantErr: true,
|
|
}, {
|
|
Name: "secret and MWH exist, missing other stuff",
|
|
Key: key,
|
|
Objects: []runtime.Object{secret,
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.ServiceReference{
|
|
Namespace: system.Namespace(),
|
|
Name: "webhook",
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
WantUpdates: []clientgotesting.UpdateActionImpl{{
|
|
Object: &admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.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,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
}},
|
|
}, {
|
|
Name: "secret and MWH exist, added fields are incorrect",
|
|
Key: key,
|
|
Objects: []runtime.Object{secret,
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.ServiceReference{
|
|
Namespace: system.Namespace(),
|
|
Name: "webhook",
|
|
// Incorrect
|
|
Path: ptr.String("incorrect"),
|
|
},
|
|
// Incorrect
|
|
CABundle: []byte("incorrect"),
|
|
},
|
|
// Incorrect (really just incomplete)
|
|
Rules: []admissionregistrationv1.RuleWithOperations{{
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"pkg.knative.dev"},
|
|
APIVersions: []string{"v1alpha1"},
|
|
Resources: []string{"innerdefaultresources/*"},
|
|
},
|
|
}},
|
|
// Incorrect
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.NeverReinvocationPolicy),
|
|
}},
|
|
},
|
|
},
|
|
WantUpdates: []clientgotesting.UpdateActionImpl{{
|
|
Object: &admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.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,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
}},
|
|
}, {
|
|
Name: "failure updating MWH",
|
|
Key: key,
|
|
WantErr: true,
|
|
WithReactors: []clientgotesting.ReactionFunc{
|
|
InduceFailure("update", "mutatingwebhookconfigurations"),
|
|
},
|
|
Objects: []runtime.Object{secret,
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.ServiceReference{
|
|
Namespace: system.Namespace(),
|
|
Name: "webhook",
|
|
// Incorrect
|
|
Path: ptr.String("incorrect"),
|
|
},
|
|
// Incorrect
|
|
CABundle: []byte("incorrect"),
|
|
},
|
|
// Incorrect (really just incomplete)
|
|
Rules: []admissionregistrationv1.RuleWithOperations{{
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"pkg.knative.dev"},
|
|
APIVersions: []string{"v1alpha1"},
|
|
Resources: []string{"innerdefaultresources/*"},
|
|
},
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
WantUpdates: []clientgotesting.UpdateActionImpl{{
|
|
Object: &admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.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,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
}},
|
|
}, {
|
|
Name: ":fire: everything is fine :fire:",
|
|
Key: key,
|
|
Objects: []runtime.Object{secret,
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.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,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
},
|
|
}, {
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.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: &admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.ServiceReference{
|
|
Namespace: system.Namespace(),
|
|
Name: "webhook",
|
|
Path: ptr.String(path),
|
|
},
|
|
CABundle: []byte("present"),
|
|
},
|
|
// A new rule is added to intercept the new type.
|
|
Rules: []admissionregistrationv1.RuleWithOperations{{
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"random.knative.dev"},
|
|
APIVersions: []string{"v2beta3"},
|
|
Resources: []string{"knoodles/*"},
|
|
},
|
|
}},
|
|
MatchPolicy: &equivalent,
|
|
NamespaceSelector: &ExclusionSelector,
|
|
ObjectSelector: &ExclusionSelector,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
}},
|
|
}, {
|
|
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",
|
|
},
|
|
},
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.ServiceReference{
|
|
Namespace: system.Namespace(),
|
|
Name: "webhook",
|
|
Path: ptr.String(path),
|
|
},
|
|
CABundle: []byte("present"),
|
|
},
|
|
// A new rule is added to intercept the new type.
|
|
Rules: []admissionregistrationv1.RuleWithOperations{{
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"apps"},
|
|
APIVersions: []string{"v1"},
|
|
Resources: []string{"deployments/*"},
|
|
},
|
|
}, {
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"random.knative.dev"},
|
|
APIVersions: []string{"v2beta3"},
|
|
Resources: []string{"knoodles/*"},
|
|
},
|
|
}},
|
|
MatchPolicy: &equivalent,
|
|
NamespaceSelector: &ExclusionSelector,
|
|
ObjectSelector: &ExclusionSelector,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
},
|
|
// 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",
|
|
},
|
|
},
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.ServiceReference{
|
|
Namespace: system.Namespace(),
|
|
Name: "webhook",
|
|
Path: ptr.String(path),
|
|
},
|
|
CABundle: []byte("present"),
|
|
},
|
|
// A new rule is added to intercept the new type.
|
|
Rules: []admissionregistrationv1.RuleWithOperations{{
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"apps"},
|
|
APIVersions: []string{"v1"},
|
|
Resources: []string{"deployments/*"},
|
|
},
|
|
}, {
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"random.knative.dev"},
|
|
APIVersions: []string{"v2beta3"},
|
|
Resources: []string{"knoodles/*"},
|
|
},
|
|
}},
|
|
MatchPolicy: &equivalent,
|
|
NamespaceSelector: &ExclusionSelector,
|
|
ObjectSelector: &ExclusionSelector,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
},
|
|
// 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: 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",
|
|
},
|
|
},
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.ServiceReference{
|
|
Namespace: system.Namespace(),
|
|
Name: "webhook",
|
|
Path: ptr.String(path),
|
|
},
|
|
CABundle: []byte("present"),
|
|
},
|
|
Rules: []admissionregistrationv1.RuleWithOperations{{
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"apps"},
|
|
APIVersions: []string{"v1"},
|
|
Resources: []string{"deployments/*"},
|
|
},
|
|
}},
|
|
MatchPolicy: &equivalent,
|
|
NamespaceSelector: &ExclusionSelector,
|
|
ObjectSelector: &ExclusionSelector,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
},
|
|
// 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",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.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: &admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.ServiceReference{
|
|
Namespace: system.Namespace(),
|
|
Name: "webhook",
|
|
Path: ptr.String(path),
|
|
},
|
|
CABundle: []byte("present"),
|
|
},
|
|
// New rules are added to intercept the new types.
|
|
Rules: []admissionregistrationv1.RuleWithOperations{{
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"pseudorandom.knative.dev"},
|
|
APIVersions: []string{"v3beta1"},
|
|
Resources: []string{"knoogles/*"},
|
|
},
|
|
}, {
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"random.knative.dev"},
|
|
APIVersions: []string{"v2beta3"},
|
|
Resources: []string{"knoodles/*"},
|
|
},
|
|
}},
|
|
MatchPolicy: &equivalent,
|
|
NamespaceSelector: &ExclusionSelector,
|
|
ObjectSelector: &ExclusionSelector,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
}},
|
|
}, {
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.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,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
},
|
|
WantUpdates: []clientgotesting.UpdateActionImpl{{
|
|
Object: &admissionregistrationv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
|
Name: name,
|
|
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1.ServiceReference{
|
|
Namespace: system.Namespace(),
|
|
Name: "webhook",
|
|
Path: ptr.String(path),
|
|
},
|
|
CABundle: []byte("present"),
|
|
},
|
|
// A new rule is added to intercept the new type.
|
|
Rules: []admissionregistrationv1.RuleWithOperations{{
|
|
Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{"random.knative.dev"},
|
|
APIVersions: []string{"v2beta3"},
|
|
Resources: []string{"knoodles/*"},
|
|
},
|
|
}},
|
|
MatchPolicy: &equivalent,
|
|
NamespaceSelector: &ExclusionSelector,
|
|
ObjectSelector: &ExclusionSelector,
|
|
ReinvocationPolicy: ptrReinvocationPolicyType(admissionregistrationv1.IfNeededReinvocationPolicy),
|
|
}},
|
|
},
|
|
}},
|
|
}}
|
|
|
|
table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler {
|
|
r := NewReconciler(name, path, secretName, kubeclient.Get(ctx), listers.GetMutatingWebhookConfigurationLister(), listers.GetSecretLister(), nil)
|
|
r.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
|
|
}
|
|
return r
|
|
}))
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
if want, got := 0, c.WorkQueue().Len(); want != got {
|
|
t.Errorf("WorkQueue.Len() = %d, wanted %d", got, want)
|
|
}
|
|
|
|
la, ok := c.Reconciler.(pkgreconciler.LeaderAware)
|
|
if !ok {
|
|
t.Fatalf("%T is not leader aware", c.Reconciler)
|
|
}
|
|
|
|
if err := la.Promote(pkgreconciler.UniversalBucket(), c.MaybeEnqueueBucketKey); err != nil {
|
|
t.Error("Promote() =", err)
|
|
}
|
|
|
|
// Queue has async moving parts so if we check at the wrong moment, this might still be 0.
|
|
if wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 250*time.Millisecond, true, func(ctx context.Context) (bool, error) {
|
|
return c.WorkQueue().Len() == 1, nil
|
|
}) != nil {
|
|
t.Error("Queue length was never 1")
|
|
}
|
|
}
|
|
|
|
func TestNewReconciler(t *testing.T) {
|
|
ctx, _ := SetupFakeContext(t)
|
|
ctx = webhook.WithOptions(ctx, webhook.Options{})
|
|
tests := []struct {
|
|
name string
|
|
selectorOption ReconcilerOption
|
|
wantSelector metav1.LabelSelector
|
|
}{
|
|
{
|
|
name: "no selector, use default",
|
|
wantSelector: ExclusionSelector,
|
|
},
|
|
{
|
|
name: "ExclusionSelector option",
|
|
selectorOption: WithSelector(ExclusionSelector),
|
|
wantSelector: ExclusionSelector,
|
|
},
|
|
{
|
|
name: "InclusionSelector option",
|
|
selectorOption: WithSelector(InclusionSelector),
|
|
wantSelector: InclusionSelector,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
client := kubeclient.Get(ctx)
|
|
mwhInformer := mwhinformer.Get(ctx)
|
|
secretInformer := secretinformer.Get(ctx)
|
|
withContext := func(ctx context.Context, b Bindable) (context.Context, error) {
|
|
return ctx, nil
|
|
}
|
|
var r *Reconciler
|
|
if tt.selectorOption == nil {
|
|
r = NewReconciler("foo", "/bar", "foosec", client, mwhInformer.Lister(), secretInformer.Lister(), withContext)
|
|
} else {
|
|
r = NewReconciler("foo", "/bar", "foosec", client, mwhInformer.Lister(), secretInformer.Lister(), withContext, tt.selectorOption)
|
|
}
|
|
if diff := cmp.Diff(r.selector, tt.wantSelector); diff != "" {
|
|
t.Errorf("Wrong selector configured. Got: %+v, want: %+v, diff: %v", r.selector, tt.wantSelector, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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",
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
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 */),
|
|
patchAddLabel("foo"),
|
|
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",
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
Labels: map[string]string{duck.BindingIncludeLabel: "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",
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchAddLabel("foo"),
|
|
},
|
|
}, {
|
|
Name: "finalizing, but not our turn.",
|
|
Key: "foo/bar",
|
|
Objects: []runtime.Object{
|
|
&TestBindable{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "foo",
|
|
Name: "bar",
|
|
DeletionTimestamp: &metav1.Time{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: 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 forbidden subject",
|
|
Key: "foo/bar",
|
|
WithReactors: []clientgotesting.ReactionFunc{
|
|
// This will cause the duck informer factory to return a Forbidden error on Get(gvr)
|
|
// The informer calls list to ensure the type exists - this will
|
|
func(a clientgotesting.Action) (handled bool, ret runtime.Object, err error) {
|
|
if a.Matches("list", "deployments") {
|
|
return true, nil, apierrs.NewForbidden(schema.GroupResource{}, "", errors.New("some-error"))
|
|
}
|
|
return false, nil, nil
|
|
},
|
|
},
|
|
Objects: []runtime.Object{
|
|
&TestBindable{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "foo",
|
|
Name: "bar",
|
|
DeletionTimestamp: &metav1.Time{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: "True",
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchRemoveFinalizer("foo", "bar", "" /* resource version */),
|
|
},
|
|
WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
|
|
Object: mustTU(t, &TestBindable{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "foo",
|
|
Name: "bar",
|
|
DeletionTimestamp: &metav1.Time{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: "SubjectUnavailable",
|
|
// prefix comes from apierrs.NewForbidden
|
|
Message: "forbidden: some-error",
|
|
}},
|
|
},
|
|
},
|
|
}),
|
|
}},
|
|
}, {
|
|
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: 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",
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchAddLabel("foo"),
|
|
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",
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchAddFinalizer("foo", "bar", "" /* resource version */),
|
|
patchAddLabel("foo"),
|
|
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",
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchAddLabel("foo"),
|
|
},
|
|
}, {
|
|
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: 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",
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchAddLabel("foo"),
|
|
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: 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",
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchAddLabel("foo"),
|
|
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",
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchAddLabel("foo"),
|
|
},
|
|
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: 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",
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchAddLabel("foo"),
|
|
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)
|
|
},
|
|
NamespaceLister: listers.GetNamespaceLister(),
|
|
}
|
|
}))
|
|
}
|
|
|
|
func TestBaseReconcileWithSubResourcesReconciler(t *testing.T) {
|
|
table := TableTest{{
|
|
Name: "create new subresource",
|
|
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",
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchAddLabel("foo"),
|
|
},
|
|
WantCreates: []runtime.Object{
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "delete resource and subresource",
|
|
Key: "foo/bar",
|
|
Objects: []runtime.Object{
|
|
&TestBindable{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "foo",
|
|
DeletionTimestamp: &metav1.Time{Time: time.Now()},
|
|
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",
|
|
}},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
},
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "bar",
|
|
Namespace: "foo",
|
|
},
|
|
},
|
|
},
|
|
WantPatches: []clientgotesting.PatchActionImpl{
|
|
patchAddLabel("foo"),
|
|
patchRemoveEnv("foo", "on-it"),
|
|
patchRemoveFinalizer("foo", "bar", "" /* resource version */),
|
|
},
|
|
WantDeletes: []clientgotesting.DeleteActionImpl{
|
|
deletedConfigmap("foo", "bar"),
|
|
},
|
|
},
|
|
}
|
|
|
|
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)
|
|
|
|
kc := kubeclient.Get(ctx)
|
|
srr := &fakeSubResourcesReconciler{
|
|
client: kc,
|
|
}
|
|
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)
|
|
},
|
|
NamespaceLister: listers.GetNamespaceLister(),
|
|
|
|
SubResourcesReconciler: srr,
|
|
}
|
|
}))
|
|
}
|
|
|
|
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 patchAddLabel(namespace string) clientgotesting.PatchActionImpl {
|
|
action := clientgotesting.PatchActionImpl{}
|
|
resource := schema.GroupVersionResource{
|
|
Group: "",
|
|
Version: "v1",
|
|
Resource: "namespaces",
|
|
}
|
|
actionImpl := clientgotesting.ActionImpl{
|
|
Namespace: namespace,
|
|
Verb: "patch",
|
|
Resource: resource,
|
|
Subresource: "",
|
|
}
|
|
action.ActionImpl = actionImpl
|
|
action.Name = namespace
|
|
action.PatchType = types.MergePatchType
|
|
|
|
patch, _ := json.Marshal(jsonLabelPatch)
|
|
action.Patch = patch
|
|
return action
|
|
}
|
|
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 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
|
|
}
|
|
|
|
func deletedConfigmap(namespace string, name string) clientgotesting.DeleteActionImpl {
|
|
action := clientgotesting.DeleteActionImpl{}
|
|
resource := schema.GroupVersionResource{
|
|
Group: "core",
|
|
Version: "v1",
|
|
Resource: "configmaps",
|
|
}
|
|
actionImpl := clientgotesting.ActionImpl{
|
|
Namespace: namespace,
|
|
Verb: "delete",
|
|
Resource: resource,
|
|
Subresource: "",
|
|
}
|
|
action.ActionImpl = actionImpl
|
|
action.Name = name
|
|
return action
|
|
}
|
|
|
|
type fakeSubResourcesReconciler struct {
|
|
client *fakek8s.Clientset
|
|
}
|
|
|
|
func (d *fakeSubResourcesReconciler) Reconcile(ctx context.Context, fb Bindable) error {
|
|
cfg := corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: fb.GetName(),
|
|
},
|
|
}
|
|
|
|
_, err := d.client.CoreV1().ConfigMaps(fb.GetNamespace()).Create(ctx, &cfg, metav1.CreateOptions{})
|
|
return err
|
|
}
|
|
|
|
func (d *fakeSubResourcesReconciler) ReconcileDeletion(ctx context.Context, fb Bindable) error {
|
|
return d.client.CoreV1().ConfigMaps(fb.GetNamespace()).Delete(ctx, fb.GetName(), metav1.DeleteOptions{})
|
|
}
|