728 lines
19 KiB
Go
728 lines
19 KiB
Go
/*
|
|
Copyright 2017 the Velero contributors.
|
|
|
|
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 backup
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
v1 "github.com/heptio/velero/pkg/apis/velero/v1"
|
|
"github.com/heptio/velero/pkg/util/collections"
|
|
velerotest "github.com/heptio/velero/pkg/util/test"
|
|
)
|
|
|
|
type mockItemHookHandler struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (h *mockItemHookHandler) handleHooks(log logrus.FieldLogger, groupResource schema.GroupResource, obj runtime.Unstructured, resourceHooks []resourceHook, phase hookPhase) error {
|
|
args := h.Called(log, groupResource, obj, resourceHooks, phase)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func TestHandleHooksSkips(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
groupResource string
|
|
item runtime.Unstructured
|
|
hooks []resourceHook
|
|
}{
|
|
{
|
|
name: "not a pod",
|
|
groupResource: "widget.group",
|
|
},
|
|
{
|
|
name: "pod without annotation / no spec hooks",
|
|
item: velerotest.UnstructuredOrDie(
|
|
`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "foo"
|
|
}
|
|
}
|
|
`,
|
|
),
|
|
},
|
|
{
|
|
name: "spec hooks not applicable",
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(
|
|
`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "foo",
|
|
"labels": {
|
|
"color": "blue"
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
),
|
|
hooks: []resourceHook{
|
|
{
|
|
name: "ns exclude",
|
|
namespaces: collections.NewIncludesExcludes().Excludes("ns"),
|
|
},
|
|
{
|
|
name: "resource exclude",
|
|
resources: collections.NewIncludesExcludes().Includes("widgets.group"),
|
|
},
|
|
{
|
|
name: "label selector mismatch",
|
|
labelSelector: parseLabelSelectorOrDie("color=green"),
|
|
},
|
|
{
|
|
name: "missing exec hook",
|
|
pre: []v1.BackupResourceHook{
|
|
{},
|
|
{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
podCommandExecutor := &velerotest.MockPodCommandExecutor{}
|
|
defer podCommandExecutor.AssertExpectations(t)
|
|
|
|
h := &defaultItemHookHandler{
|
|
podCommandExecutor: podCommandExecutor,
|
|
}
|
|
|
|
groupResource := schema.ParseGroupResource(test.groupResource)
|
|
err := h.handleHooks(velerotest.NewLogger(), groupResource, test.item, test.hooks, hookPhasePre)
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleHooks(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
phase hookPhase
|
|
groupResource string
|
|
item runtime.Unstructured
|
|
hooks []resourceHook
|
|
hookErrorsByContainer map[string]error
|
|
expectedError error
|
|
expectedPodHook *v1.ExecHook
|
|
expectedPodHookError error
|
|
}{
|
|
{
|
|
name: "pod, no annotation, spec (multiple pre hooks) = run spec",
|
|
phase: hookPhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name"
|
|
}
|
|
}`),
|
|
hooks: []resourceHook{
|
|
{
|
|
name: "hook1",
|
|
pre: []v1.BackupResourceHook{
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "1a",
|
|
Command: []string{"pre-1a"},
|
|
},
|
|
},
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "1b",
|
|
Command: []string{"pre-1b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "hook2",
|
|
pre: []v1.BackupResourceHook{
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "2a",
|
|
Command: []string{"2a"},
|
|
},
|
|
},
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "2b",
|
|
Command: []string{"2b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, no annotation, spec (multiple post hooks) = run spec",
|
|
phase: hookPhasePost,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name"
|
|
}
|
|
}`),
|
|
hooks: []resourceHook{
|
|
{
|
|
name: "hook1",
|
|
post: []v1.BackupResourceHook{
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "1a",
|
|
Command: []string{"pre-1a"},
|
|
},
|
|
},
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "1b",
|
|
Command: []string{"pre-1b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "hook2",
|
|
post: []v1.BackupResourceHook{
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "2a",
|
|
Command: []string{"2a"},
|
|
},
|
|
},
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "2b",
|
|
Command: []string{"2b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, annotation (legacy), no spec = run annotation",
|
|
phase: hookPhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"hook.backup.velero.io/container": "c",
|
|
"hook.backup.velero.io/command": "/bin/ls"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &v1.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, annotation (pre), no spec = run annotation",
|
|
phase: hookPhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"pre.hook.backup.velero.io/container": "c",
|
|
"pre.hook.backup.velero.io/command": "/bin/ls"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &v1.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, annotation (post), no spec = run annotation",
|
|
phase: hookPhasePost,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"post.hook.backup.velero.io/container": "c",
|
|
"post.hook.backup.velero.io/command": "/bin/ls"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &v1.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, annotation & spec = run annotation",
|
|
phase: hookPhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"hook.backup.velero.io/container": "c",
|
|
"hook.backup.velero.io/command": "/bin/ls"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &v1.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
},
|
|
hooks: []resourceHook{
|
|
{
|
|
name: "hook1",
|
|
pre: []v1.BackupResourceHook{
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "1a",
|
|
Command: []string{"1a"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, annotation, onError=fail = return error",
|
|
phase: hookPhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"hook.backup.velero.io/container": "c",
|
|
"hook.backup.velero.io/command": "/bin/ls",
|
|
"hook.backup.velero.io/on-error": "Fail"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &v1.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
OnError: v1.HookErrorModeFail,
|
|
},
|
|
expectedPodHookError: errors.New("pod hook error"),
|
|
expectedError: errors.New("pod hook error"),
|
|
},
|
|
{
|
|
name: "pod, annotation, onError=continue = return nil",
|
|
phase: hookPhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"hook.backup.velero.io/container": "c",
|
|
"hook.backup.velero.io/command": "/bin/ls",
|
|
"hook.backup.velero.io/on-error": "Continue"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &v1.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
OnError: v1.HookErrorModeContinue,
|
|
},
|
|
expectedPodHookError: errors.New("pod hook error"),
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "pod, spec, onError=fail = don't run other hooks",
|
|
phase: hookPhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name"
|
|
}
|
|
}`),
|
|
hooks: []resourceHook{
|
|
{
|
|
name: "hook1",
|
|
pre: []v1.BackupResourceHook{
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "1a",
|
|
Command: []string{"1a"},
|
|
OnError: v1.HookErrorModeContinue,
|
|
},
|
|
},
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "1b",
|
|
Command: []string{"1b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "hook2",
|
|
pre: []v1.BackupResourceHook{
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "2",
|
|
Command: []string{"2"},
|
|
OnError: v1.HookErrorModeFail,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "hook3",
|
|
pre: []v1.BackupResourceHook{
|
|
{
|
|
Exec: &v1.ExecHook{
|
|
Container: "3",
|
|
Command: []string{"3"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
hookErrorsByContainer: map[string]error{
|
|
"1a": errors.New("1a error, but continue"),
|
|
"2": errors.New("2 error, fail"),
|
|
},
|
|
expectedError: errors.New("2 error, fail"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
podCommandExecutor := &velerotest.MockPodCommandExecutor{}
|
|
defer podCommandExecutor.AssertExpectations(t)
|
|
|
|
h := &defaultItemHookHandler{
|
|
podCommandExecutor: podCommandExecutor,
|
|
}
|
|
|
|
if test.expectedPodHook != nil {
|
|
podCommandExecutor.On("ExecutePodCommand", mock.Anything, test.item.UnstructuredContent(), "ns", "name", "<from-annotation>", test.expectedPodHook).Return(test.expectedPodHookError)
|
|
} else {
|
|
hookLoop:
|
|
for _, resourceHook := range test.hooks {
|
|
for _, hook := range resourceHook.pre {
|
|
hookError := test.hookErrorsByContainer[hook.Exec.Container]
|
|
podCommandExecutor.On("ExecutePodCommand", mock.Anything, test.item.UnstructuredContent(), "ns", "name", resourceHook.name, hook.Exec).Return(hookError)
|
|
if hookError != nil && hook.Exec.OnError == v1.HookErrorModeFail {
|
|
break hookLoop
|
|
}
|
|
}
|
|
for _, hook := range resourceHook.post {
|
|
hookError := test.hookErrorsByContainer[hook.Exec.Container]
|
|
podCommandExecutor.On("ExecutePodCommand", mock.Anything, test.item.UnstructuredContent(), "ns", "name", resourceHook.name, hook.Exec).Return(hookError)
|
|
if hookError != nil && hook.Exec.OnError == v1.HookErrorModeFail {
|
|
break hookLoop
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
groupResource := schema.ParseGroupResource(test.groupResource)
|
|
err := h.handleHooks(velerotest.NewLogger(), groupResource, test.item, test.hooks, test.phase)
|
|
|
|
if test.expectedError != nil {
|
|
assert.EqualError(t, err, test.expectedError.Error())
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPodExecHookFromAnnotations(t *testing.T) {
|
|
phases := []hookPhase{"", hookPhasePre, hookPhasePost}
|
|
for _, phase := range phases {
|
|
tests := []struct {
|
|
name string
|
|
annotations map[string]string
|
|
expectedHook *v1.ExecHook
|
|
}{
|
|
{
|
|
name: "missing command annotation",
|
|
expectedHook: nil,
|
|
},
|
|
{
|
|
name: "malformed command json array",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "[blarg",
|
|
},
|
|
expectedHook: &v1.ExecHook{
|
|
Command: []string{"[blarg"},
|
|
},
|
|
},
|
|
{
|
|
name: "valid command json array",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): `["a","b","c"]`,
|
|
},
|
|
expectedHook: &v1.ExecHook{
|
|
Command: []string{"a", "b", "c"},
|
|
},
|
|
},
|
|
{
|
|
name: "command as a string",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
},
|
|
expectedHook: &v1.ExecHook{
|
|
Command: []string{"/usr/bin/foo"},
|
|
},
|
|
},
|
|
{
|
|
name: "hook mode set to continue",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
phasedKey(phase, podBackupHookOnErrorAnnotationKey): string(v1.HookErrorModeContinue),
|
|
},
|
|
expectedHook: &v1.ExecHook{
|
|
Command: []string{"/usr/bin/foo"},
|
|
OnError: v1.HookErrorModeContinue,
|
|
},
|
|
},
|
|
{
|
|
name: "hook mode set to fail",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
phasedKey(phase, podBackupHookOnErrorAnnotationKey): string(v1.HookErrorModeFail),
|
|
},
|
|
expectedHook: &v1.ExecHook{
|
|
Command: []string{"/usr/bin/foo"},
|
|
OnError: v1.HookErrorModeFail,
|
|
},
|
|
},
|
|
{
|
|
name: "use the specified timeout",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
phasedKey(phase, podBackupHookTimeoutAnnotationKey): "5m3s",
|
|
},
|
|
expectedHook: &v1.ExecHook{
|
|
Command: []string{"/usr/bin/foo"},
|
|
Timeout: metav1.Duration{Duration: 5*time.Minute + 3*time.Second},
|
|
},
|
|
},
|
|
{
|
|
name: "invalid timeout is ignored",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
phasedKey(phase, podBackupHookTimeoutAnnotationKey): "invalid",
|
|
},
|
|
expectedHook: &v1.ExecHook{
|
|
Command: []string{"/usr/bin/foo"},
|
|
},
|
|
},
|
|
{
|
|
name: "use the specified container",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookContainerAnnotationKey): "some-container",
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
},
|
|
expectedHook: &v1.ExecHook{
|
|
Container: "some-container",
|
|
Command: []string{"/usr/bin/foo"},
|
|
},
|
|
},
|
|
{
|
|
name: "legacy ark-based annotations are supported",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, arkPodBackupHookContainerAnnotationKey): "some-container",
|
|
phasedKey(phase, arkPodBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
},
|
|
expectedHook: &v1.ExecHook{
|
|
Container: "some-container",
|
|
Command: []string{"/usr/bin/foo"},
|
|
},
|
|
},
|
|
{
|
|
name: "when both current and legacy ark-based annotations are specified, current takes precedence",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookContainerAnnotationKey): "current-container",
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/current",
|
|
phasedKey(phase, podBackupHookOnErrorAnnotationKey): string(v1.HookErrorModeContinue),
|
|
phasedKey(phase, podBackupHookTimeoutAnnotationKey): "10m",
|
|
|
|
phasedKey(phase, arkPodBackupHookContainerAnnotationKey): "legacy-container",
|
|
phasedKey(phase, arkPodBackupHookCommandAnnotationKey): "/usr/bin/legacy",
|
|
phasedKey(phase, arkPodBackupHookOnErrorAnnotationKey): string(v1.HookErrorModeFail),
|
|
phasedKey(phase, arkPodBackupHookTimeoutAnnotationKey): "5m",
|
|
},
|
|
expectedHook: &v1.ExecHook{
|
|
Container: "current-container",
|
|
Command: []string{"/usr/bin/current"},
|
|
OnError: v1.HookErrorModeContinue,
|
|
Timeout: metav1.Duration{Duration: 10 * time.Minute},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%s (phase=%q)", test.name, phase), func(t *testing.T) {
|
|
hook := getPodExecHookFromAnnotations(test.annotations, phase)
|
|
assert.Equal(t, test.expectedHook, hook)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceHookApplicableTo(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includedNamespaces []string
|
|
excludedNamespaces []string
|
|
includedResources []string
|
|
excludedResources []string
|
|
labelSelector string
|
|
namespace string
|
|
resource schema.GroupResource
|
|
labels labels.Set
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "allow anything",
|
|
namespace: "foo",
|
|
resource: schema.GroupResource{Group: "foo", Resource: "bar"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "namespace in included list",
|
|
includedNamespaces: []string{"a", "b"},
|
|
excludedNamespaces: []string{"c", "d"},
|
|
namespace: "b",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "namespace not in included list",
|
|
includedNamespaces: []string{"a", "b"},
|
|
namespace: "c",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "namespace excluded",
|
|
excludedNamespaces: []string{"a", "b"},
|
|
namespace: "a",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "resource in included list",
|
|
includedResources: []string{"foo.a", "bar.b"},
|
|
excludedResources: []string{"baz.c"},
|
|
resource: schema.GroupResource{Group: "a", Resource: "foo"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "resource not in included list",
|
|
includedResources: []string{"foo.a", "bar.b"},
|
|
resource: schema.GroupResource{Group: "c", Resource: "baz"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "resource excluded",
|
|
excludedResources: []string{"foo.a", "bar.b"},
|
|
resource: schema.GroupResource{Group: "b", Resource: "bar"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "label selector matches",
|
|
labelSelector: "a=b",
|
|
labels: labels.Set{"a": "b"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "label selector doesn't match",
|
|
labelSelector: "a=b",
|
|
labels: labels.Set{"a": "c"},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
h := resourceHook{
|
|
namespaces: collections.NewIncludesExcludes().Includes(test.includedNamespaces...).Excludes(test.excludedNamespaces...),
|
|
resources: collections.NewIncludesExcludes().Includes(test.includedResources...).Excludes(test.excludedResources...),
|
|
}
|
|
if test.labelSelector != "" {
|
|
selector, err := labels.Parse(test.labelSelector)
|
|
require.NoError(t, err)
|
|
h.labelSelector = selector
|
|
}
|
|
|
|
result := h.applicableTo(test.resource, test.namespace, test.labels)
|
|
assert.Equal(t, test.expected, result)
|
|
})
|
|
}
|
|
}
|