2017-08-02 17:27:17 +00:00
|
|
|
/*
|
2018-01-02 18:51:49 +00:00
|
|
|
Copyright 2017 the Heptio Ark contributors.
|
2017-08-02 17:27:17 +00:00
|
|
|
|
|
|
|
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 (
|
|
|
|
"bytes"
|
|
|
|
"compress/gzip"
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2017-10-02 20:53:08 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
2017-10-05 23:36:04 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
2017-10-02 20:53:08 +00:00
|
|
|
"k8s.io/apimachinery/pkg/labels"
|
2017-08-02 17:27:17 +00:00
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
|
|
|
|
"github.com/heptio/ark/pkg/apis/ark/v1"
|
2017-10-02 20:53:08 +00:00
|
|
|
"github.com/heptio/ark/pkg/client"
|
2017-11-15 02:35:02 +00:00
|
|
|
"github.com/heptio/ark/pkg/cloudprovider"
|
2017-10-02 20:53:08 +00:00
|
|
|
"github.com/heptio/ark/pkg/discovery"
|
2017-08-02 17:27:17 +00:00
|
|
|
"github.com/heptio/ark/pkg/util/collections"
|
2017-10-02 20:53:08 +00:00
|
|
|
kubeutil "github.com/heptio/ark/pkg/util/kube"
|
|
|
|
arktest "github.com/heptio/ark/pkg/util/test"
|
2017-08-02 17:27:17 +00:00
|
|
|
)
|
|
|
|
|
2017-10-11 18:31:28 +00:00
|
|
|
var (
|
|
|
|
trueVal = true
|
|
|
|
falseVal = false
|
|
|
|
truePointer = &trueVal
|
|
|
|
falsePointer = &falseVal
|
|
|
|
)
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
type fakeAction struct {
|
2017-11-15 02:35:02 +00:00
|
|
|
selector ResourceSelector
|
2017-10-02 20:53:08 +00:00
|
|
|
ids []string
|
2017-11-15 02:35:02 +00:00
|
|
|
backups []v1.Backup
|
2017-10-02 20:53:08 +00:00
|
|
|
additionalItems []ResourceIdentifier
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2017-11-15 02:35:02 +00:00
|
|
|
var _ ItemAction = &fakeAction{}
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-11-15 02:35:02 +00:00
|
|
|
func newFakeAction(resource string) *fakeAction {
|
|
|
|
return (&fakeAction{}).ForResource(resource)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *fakeAction) Execute(item runtime.Unstructured, backup *v1.Backup) (runtime.Unstructured, []ResourceIdentifier, error) {
|
2017-10-02 20:53:08 +00:00
|
|
|
metadata, err := meta.Accessor(item)
|
2017-08-02 17:27:17 +00:00
|
|
|
if err != nil {
|
2017-11-15 02:35:02 +00:00
|
|
|
return item, a.additionalItems, err
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
2017-10-02 20:53:08 +00:00
|
|
|
a.ids = append(a.ids, kubeutil.NamespaceAndName(metadata))
|
2017-11-15 02:35:02 +00:00
|
|
|
a.backups = append(a.backups, *backup)
|
|
|
|
|
|
|
|
return item, a.additionalItems, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *fakeAction) AppliesTo() (ResourceSelector, error) {
|
|
|
|
return a.selector, nil
|
|
|
|
}
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-11-15 02:35:02 +00:00
|
|
|
func (a *fakeAction) ForResource(resource string) *fakeAction {
|
|
|
|
a.selector.IncludedResources = []string{resource}
|
|
|
|
return a
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestResolveActions(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
2017-11-15 02:35:02 +00:00
|
|
|
input []ItemAction
|
|
|
|
expected []resolvedAction
|
2017-08-02 17:27:17 +00:00
|
|
|
resourcesWithErrors []string
|
|
|
|
expectError bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "empty input",
|
2017-11-15 02:35:02 +00:00
|
|
|
input: []ItemAction{},
|
|
|
|
expected: nil,
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
2017-11-15 02:35:02 +00:00
|
|
|
name: "resolve error",
|
|
|
|
input: []ItemAction{&fakeAction{selector: ResourceSelector{LabelSelector: "=invalid-selector"}}},
|
|
|
|
expected: nil,
|
2017-08-02 17:27:17 +00:00
|
|
|
expectError: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "resolved",
|
2017-11-15 02:35:02 +00:00
|
|
|
input: []ItemAction{newFakeAction("foo"), newFakeAction("bar")},
|
|
|
|
expected: []resolvedAction{
|
|
|
|
{
|
|
|
|
ItemAction: newFakeAction("foo"),
|
|
|
|
resourceIncludesExcludes: collections.NewIncludesExcludes().Includes("foodies.somegroup"),
|
|
|
|
namespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
|
|
|
selector: labels.Everything(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ItemAction: newFakeAction("bar"),
|
|
|
|
resourceIncludesExcludes: collections.NewIncludesExcludes().Includes("barnacles.anothergroup"),
|
|
|
|
namespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
|
|
|
selector: labels.Everything(),
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2017-08-24 23:44:01 +00:00
|
|
|
resources := map[schema.GroupVersionResource]schema.GroupVersionResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "foo"}: {Group: "somegroup", Resource: "foodies"},
|
|
|
|
{Resource: "fie"}: {Group: "somegroup", Resource: "fields"},
|
|
|
|
{Resource: "bar"}: {Group: "anothergroup", Resource: "barnacles"},
|
|
|
|
{Resource: "baz"}: {Group: "anothergroup", Resource: "bazaars"},
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
2017-10-02 20:53:08 +00:00
|
|
|
discoveryHelper := arktest.NewFakeDiscoveryHelper(false, resources)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-11-15 02:35:02 +00:00
|
|
|
actual, err := resolveActions(test.input, discoveryHelper)
|
2017-08-02 17:27:17 +00:00
|
|
|
gotError := err != nil
|
|
|
|
|
|
|
|
if e, a := test.expectError, gotError; e != a {
|
|
|
|
t.Fatalf("error: expected %t, got %t", e, a)
|
|
|
|
}
|
|
|
|
if test.expectError {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-10-02 20:53:08 +00:00
|
|
|
assert.Equal(t, test.expected, actual)
|
2017-08-02 17:27:17 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetResourceIncludesExcludes(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
includes []string
|
|
|
|
excludes []string
|
|
|
|
resourcesWithErrors []string
|
|
|
|
expectedIncludes []string
|
|
|
|
expectedExcludes []string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "no input",
|
|
|
|
expectedIncludes: []string{},
|
|
|
|
expectedExcludes: []string{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "wildcard includes",
|
|
|
|
includes: []string{"*", "asdf"},
|
|
|
|
excludes: []string{},
|
|
|
|
expectedIncludes: []string{"*"},
|
|
|
|
expectedExcludes: []string{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "wildcard excludes aren't allowed or resolved",
|
|
|
|
includes: []string{},
|
|
|
|
excludes: []string{"*"},
|
|
|
|
expectedIncludes: []string{},
|
|
|
|
expectedExcludes: []string{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "resolution works",
|
|
|
|
includes: []string{"foo", "fie"},
|
|
|
|
excludes: []string{"bar", "baz"},
|
|
|
|
expectedIncludes: []string{"foodies.somegroup", "fields.somegroup"},
|
|
|
|
expectedExcludes: []string{"barnacles.anothergroup", "bazaars.anothergroup"},
|
|
|
|
},
|
2017-08-11 15:05:56 +00:00
|
|
|
{
|
|
|
|
name: "some unresolvable",
|
|
|
|
includes: []string{"foo", "fie", "bad1"},
|
|
|
|
excludes: []string{"bar", "baz", "bad2"},
|
|
|
|
expectedIncludes: []string{"foodies.somegroup", "fields.somegroup"},
|
|
|
|
expectedExcludes: []string{"barnacles.anothergroup", "bazaars.anothergroup"},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2017-08-24 23:44:01 +00:00
|
|
|
resources := map[schema.GroupVersionResource]schema.GroupVersionResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "foo"}: {Group: "somegroup", Resource: "foodies"},
|
|
|
|
{Resource: "fie"}: {Group: "somegroup", Resource: "fields"},
|
|
|
|
{Resource: "bar"}: {Group: "anothergroup", Resource: "barnacles"},
|
|
|
|
{Resource: "baz"}: {Group: "anothergroup", Resource: "bazaars"},
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
2017-10-02 20:53:08 +00:00
|
|
|
discoveryHelper := arktest.NewFakeDiscoveryHelper(false, resources)
|
2017-10-05 23:36:04 +00:00
|
|
|
|
2017-10-02 20:53:08 +00:00
|
|
|
actual := getResourceIncludesExcludes(discoveryHelper, test.includes, test.excludes)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
|
|
|
sort.Strings(test.expectedIncludes)
|
|
|
|
actualIncludes := actual.GetIncludes()
|
|
|
|
sort.Strings(actualIncludes)
|
|
|
|
if e, a := test.expectedIncludes, actualIncludes; !reflect.DeepEqual(e, a) {
|
|
|
|
t.Errorf("includes: expected %v, got %v", e, a)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(test.expectedExcludes)
|
|
|
|
actualExcludes := actual.GetExcludes()
|
|
|
|
sort.Strings(actualExcludes)
|
|
|
|
if e, a := test.expectedExcludes, actualExcludes; !reflect.DeepEqual(e, a) {
|
|
|
|
t.Errorf("excludes: expected %v, got %v", e, a)
|
|
|
|
t.Errorf("excludes: expected %v, got %v", len(e), len(a))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetNamespaceIncludesExcludes(t *testing.T) {
|
|
|
|
backup := &v1.Backup{
|
|
|
|
Spec: v1.BackupSpec{
|
|
|
|
IncludedResources: []string{"foo", "bar"},
|
|
|
|
ExcludedResources: []string{"fie", "baz"},
|
|
|
|
IncludedNamespaces: []string{"a", "b", "c"},
|
|
|
|
ExcludedNamespaces: []string{"d", "e", "f"},
|
|
|
|
TTL: metav1.Duration{Duration: 1 * time.Hour},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
ns := getNamespaceIncludesExcludes(backup)
|
|
|
|
|
|
|
|
actualIncludes := ns.GetIncludes()
|
|
|
|
sort.Strings(actualIncludes)
|
|
|
|
if e, a := backup.Spec.IncludedNamespaces, actualIncludes; !reflect.DeepEqual(e, a) {
|
|
|
|
t.Errorf("includes: expected %v, got %v", e, a)
|
|
|
|
}
|
|
|
|
|
|
|
|
actualExcludes := ns.GetExcludes()
|
|
|
|
sort.Strings(actualExcludes)
|
|
|
|
if e, a := backup.Spec.ExcludedNamespaces, actualExcludes; !reflect.DeepEqual(e, a) {
|
|
|
|
t.Errorf("excludes: expected %v, got %v", e, a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-02 20:53:08 +00:00
|
|
|
var (
|
|
|
|
v1Group = &metav1.APIResourceList{
|
|
|
|
GroupVersion: "v1",
|
2017-11-02 15:36:52 +00:00
|
|
|
APIResources: []metav1.APIResource{configMapsResource, podsResource, namespacesResource},
|
2017-10-02 20:53:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
configMapsResource = metav1.APIResource{
|
|
|
|
Name: "configmaps",
|
|
|
|
SingularName: "configmap",
|
|
|
|
Namespaced: true,
|
|
|
|
Kind: "ConfigMap",
|
|
|
|
Verbs: metav1.Verbs([]string{"create", "update", "get", "list", "watch", "delete"}),
|
|
|
|
ShortNames: []string{"cm"},
|
|
|
|
Categories: []string{"all"},
|
|
|
|
}
|
|
|
|
|
|
|
|
podsResource = metav1.APIResource{
|
|
|
|
Name: "pods",
|
|
|
|
SingularName: "pod",
|
|
|
|
Namespaced: true,
|
|
|
|
Kind: "Pod",
|
|
|
|
Verbs: metav1.Verbs([]string{"create", "update", "get", "list", "watch", "delete"}),
|
|
|
|
ShortNames: []string{"po"},
|
|
|
|
Categories: []string{"all"},
|
|
|
|
}
|
|
|
|
|
|
|
|
rbacGroup = &metav1.APIResourceList{
|
|
|
|
GroupVersion: "rbac.authorization.k8s.io/v1beta1",
|
|
|
|
APIResources: []metav1.APIResource{rolesResource},
|
|
|
|
}
|
|
|
|
|
|
|
|
rolesResource = metav1.APIResource{
|
|
|
|
Name: "roles",
|
|
|
|
SingularName: "role",
|
|
|
|
Namespaced: true,
|
|
|
|
Kind: "Role",
|
|
|
|
Verbs: metav1.Verbs([]string{"create", "update", "get", "list", "watch", "delete"}),
|
|
|
|
}
|
|
|
|
|
2017-11-02 15:36:52 +00:00
|
|
|
namespacesResource = metav1.APIResource{
|
|
|
|
Name: "namespaces",
|
|
|
|
SingularName: "namespace",
|
|
|
|
Namespaced: false,
|
|
|
|
Kind: "Namespace",
|
|
|
|
Verbs: metav1.Verbs([]string{"create", "update", "get", "list", "watch", "delete"}),
|
|
|
|
}
|
|
|
|
|
2017-10-02 20:53:08 +00:00
|
|
|
certificatesGroup = &metav1.APIResourceList{
|
|
|
|
GroupVersion: "certificates.k8s.io/v1beta1",
|
|
|
|
APIResources: []metav1.APIResource{certificateSigningRequestsResource},
|
|
|
|
}
|
|
|
|
|
|
|
|
certificateSigningRequestsResource = metav1.APIResource{
|
|
|
|
Name: "certificatesigningrequests",
|
|
|
|
SingularName: "certificatesigningrequest",
|
|
|
|
Namespaced: false,
|
|
|
|
Kind: "CertificateSigningRequest",
|
|
|
|
Verbs: metav1.Verbs([]string{"create", "update", "get", "list", "watch", "delete"}),
|
|
|
|
ShortNames: []string{"csr"},
|
|
|
|
}
|
|
|
|
|
|
|
|
extensionsGroup = &metav1.APIResourceList{
|
|
|
|
GroupVersion: "extensions/v1beta1",
|
|
|
|
APIResources: []metav1.APIResource{deploymentsResource, networkPoliciesResource},
|
|
|
|
}
|
|
|
|
|
|
|
|
extensionsGroupVersion = schema.GroupVersion{
|
|
|
|
Group: "extensions",
|
|
|
|
Version: "v1beta1",
|
|
|
|
}
|
|
|
|
|
|
|
|
appsGroup = &metav1.APIResourceList{
|
|
|
|
GroupVersion: "apps/v1beta1",
|
|
|
|
APIResources: []metav1.APIResource{deploymentsResource},
|
|
|
|
}
|
|
|
|
|
|
|
|
appsGroupVersion = schema.GroupVersion{
|
|
|
|
Group: "apps",
|
|
|
|
Version: "v1beta1",
|
|
|
|
}
|
|
|
|
|
|
|
|
deploymentsResource = metav1.APIResource{
|
|
|
|
Name: "deployments",
|
|
|
|
SingularName: "deployment",
|
|
|
|
Namespaced: true,
|
|
|
|
Kind: "Deployment",
|
|
|
|
Verbs: metav1.Verbs([]string{"create", "update", "get", "list", "watch", "delete"}),
|
|
|
|
ShortNames: []string{"deploy"},
|
|
|
|
Categories: []string{"all"},
|
|
|
|
}
|
|
|
|
|
|
|
|
networkingGroup = &metav1.APIResourceList{
|
|
|
|
GroupVersion: "networking.k8s.io/v1",
|
|
|
|
APIResources: []metav1.APIResource{networkPoliciesResource},
|
|
|
|
}
|
|
|
|
|
|
|
|
networkingGroupVersion = schema.GroupVersion{
|
|
|
|
Group: "networking.k8s.io",
|
|
|
|
Version: "v1",
|
|
|
|
}
|
|
|
|
|
|
|
|
networkPoliciesResource = metav1.APIResource{
|
|
|
|
Name: "networkpolicies",
|
|
|
|
SingularName: "networkpolicy",
|
|
|
|
Namespaced: true,
|
|
|
|
Kind: "Deployment",
|
|
|
|
Verbs: metav1.Verbs([]string{"create", "update", "get", "list", "watch", "delete"}),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func parseLabelSelectorOrDie(s string) labels.Selector {
|
|
|
|
ret, err := labels.Parse(s)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBackup(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
backup *v1.Backup
|
|
|
|
expectedNamespaces *collections.IncludesExcludes
|
|
|
|
expectedResources *collections.IncludesExcludes
|
|
|
|
expectedLabelSelector string
|
|
|
|
expectedHooks []resourceHook
|
|
|
|
backupGroupErrors map[*metav1.APIResourceList]error
|
|
|
|
expectedError error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "happy path, no actions, no label selector, no hooks, no errors",
|
|
|
|
backup: &v1.Backup{
|
|
|
|
Spec: v1.BackupSpec{
|
|
|
|
// cm - shortcut in legacy api group
|
|
|
|
// csr - shortcut in certificates.k8s.io api group
|
|
|
|
// roles - fully qualified in rbac.authorization.k8s.io api group
|
|
|
|
IncludedResources: []string{"cm", "csr", "roles"},
|
|
|
|
IncludedNamespaces: []string{"a", "b"},
|
|
|
|
ExcludedNamespaces: []string{"c", "d"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedNamespaces: collections.NewIncludesExcludes().Includes("a", "b").Excludes("c", "d"),
|
|
|
|
expectedResources: collections.NewIncludesExcludes().Includes("configmaps", "certificatesigningrequests.certificates.k8s.io", "roles.rbac.authorization.k8s.io"),
|
|
|
|
expectedHooks: []resourceHook{},
|
|
|
|
backupGroupErrors: map[*metav1.APIResourceList]error{
|
|
|
|
v1Group: nil,
|
|
|
|
certificatesGroup: nil,
|
|
|
|
rbacGroup: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "label selector",
|
|
|
|
backup: &v1.Backup{
|
|
|
|
Spec: v1.BackupSpec{
|
|
|
|
LabelSelector: &metav1.LabelSelector{
|
|
|
|
MatchLabels: map[string]string{"a": "b"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedNamespaces: collections.NewIncludesExcludes(),
|
|
|
|
expectedResources: collections.NewIncludesExcludes(),
|
|
|
|
expectedHooks: []resourceHook{},
|
|
|
|
expectedLabelSelector: "a=b",
|
|
|
|
backupGroupErrors: map[*metav1.APIResourceList]error{
|
|
|
|
v1Group: nil,
|
|
|
|
certificatesGroup: nil,
|
|
|
|
rbacGroup: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "backupGroup errors",
|
|
|
|
backup: &v1.Backup{},
|
|
|
|
expectedNamespaces: collections.NewIncludesExcludes(),
|
|
|
|
expectedResources: collections.NewIncludesExcludes(),
|
|
|
|
expectedHooks: []resourceHook{},
|
|
|
|
backupGroupErrors: map[*metav1.APIResourceList]error{
|
|
|
|
v1Group: errors.New("v1 error"),
|
|
|
|
certificatesGroup: nil,
|
|
|
|
rbacGroup: errors.New("rbac error"),
|
|
|
|
},
|
|
|
|
expectedError: errors.New("[v1 error, rbac error]"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "hooks",
|
|
|
|
backup: &v1.Backup{
|
|
|
|
Spec: v1.BackupSpec{
|
|
|
|
Hooks: v1.BackupHooks{
|
|
|
|
Resources: []v1.BackupResourceHookSpec{
|
|
|
|
{
|
|
|
|
Name: "hook1",
|
|
|
|
IncludedNamespaces: []string{"a"},
|
|
|
|
ExcludedNamespaces: []string{"b"},
|
|
|
|
IncludedResources: []string{"cm"},
|
|
|
|
ExcludedResources: []string{"roles"},
|
|
|
|
LabelSelector: &metav1.LabelSelector{
|
|
|
|
MatchLabels: map[string]string{"1": "2"},
|
|
|
|
},
|
|
|
|
Hooks: []v1.BackupResourceHook{
|
|
|
|
{
|
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Command: []string{"ls", "/tmp"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedNamespaces: collections.NewIncludesExcludes(),
|
|
|
|
expectedResources: collections.NewIncludesExcludes(),
|
|
|
|
expectedHooks: []resourceHook{
|
|
|
|
{
|
|
|
|
name: "hook1",
|
|
|
|
namespaces: collections.NewIncludesExcludes().Includes("a").Excludes("b"),
|
|
|
|
resources: collections.NewIncludesExcludes().Includes("configmaps").Excludes("roles.rbac.authorization.k8s.io"),
|
|
|
|
labelSelector: parseLabelSelectorOrDie("1=2"),
|
2018-01-04 15:27:12 +00:00
|
|
|
pre: []v1.BackupResourceHook{
|
2017-10-02 20:53:08 +00:00
|
|
|
{
|
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Command: []string{"ls", "/tmp"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
backupGroupErrors: map[*metav1.APIResourceList]error{
|
|
|
|
v1Group: nil,
|
|
|
|
certificatesGroup: nil,
|
|
|
|
rbacGroup: nil,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
discoveryHelper := &arktest.FakeDiscoveryHelper{
|
|
|
|
Mapper: &arktest.FakeMapper{
|
|
|
|
Resources: map[schema.GroupVersionResource]schema.GroupVersionResource{
|
2017-10-26 21:22:39 +00:00
|
|
|
{Resource: "cm"}: {Group: "", Version: "v1", Resource: "configmaps"},
|
|
|
|
{Resource: "csr"}: {Group: "certificates.k8s.io", Version: "v1beta1", Resource: "certificatesigningrequests"},
|
|
|
|
{Resource: "roles"}: {Group: "rbac.authorization.k8s.io", Version: "v1beta1", Resource: "roles"},
|
2017-10-02 20:53:08 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceList: []*metav1.APIResourceList{
|
|
|
|
v1Group,
|
|
|
|
certificatesGroup,
|
|
|
|
rbacGroup,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
dynamicFactory := &arktest.FakeDynamicFactory{}
|
|
|
|
|
|
|
|
podCommandExecutor := &mockPodCommandExecutor{}
|
|
|
|
defer podCommandExecutor.AssertExpectations(t)
|
|
|
|
|
|
|
|
b, err := NewKubernetesBackupper(
|
|
|
|
discoveryHelper,
|
|
|
|
dynamicFactory,
|
|
|
|
podCommandExecutor,
|
2017-11-15 02:35:02 +00:00
|
|
|
nil,
|
2017-10-02 20:53:08 +00:00
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
kb := b.(*kubernetesBackupper)
|
|
|
|
|
|
|
|
groupBackupperFactory := &mockGroupBackupperFactory{}
|
|
|
|
defer groupBackupperFactory.AssertExpectations(t)
|
|
|
|
kb.groupBackupperFactory = groupBackupperFactory
|
|
|
|
|
|
|
|
groupBackupper := &mockGroupBackupper{}
|
|
|
|
defer groupBackupper.AssertExpectations(t)
|
|
|
|
|
|
|
|
groupBackupperFactory.On("newGroupBackupper",
|
|
|
|
mock.Anything, // log
|
|
|
|
test.backup,
|
|
|
|
test.expectedNamespaces,
|
|
|
|
test.expectedResources,
|
|
|
|
test.expectedLabelSelector,
|
|
|
|
dynamicFactory,
|
|
|
|
discoveryHelper,
|
|
|
|
map[itemKey]struct{}{}, // backedUpItems
|
|
|
|
cohabitatingResources,
|
2017-11-15 02:35:02 +00:00
|
|
|
mock.Anything,
|
2017-10-02 20:53:08 +00:00
|
|
|
kb.podCommandExecutor,
|
|
|
|
mock.Anything, // tarWriter
|
|
|
|
test.expectedHooks,
|
2017-11-15 02:35:02 +00:00
|
|
|
mock.Anything,
|
2017-10-02 20:53:08 +00:00
|
|
|
).Return(groupBackupper)
|
|
|
|
|
|
|
|
for group, err := range test.backupGroupErrors {
|
|
|
|
groupBackupper.On("backupGroup", group).Return(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var backupFile, logFile bytes.Buffer
|
|
|
|
|
2017-11-15 02:35:02 +00:00
|
|
|
err = b.Backup(test.backup, &backupFile, &logFile, nil)
|
2017-10-02 20:53:08 +00:00
|
|
|
defer func() {
|
|
|
|
// print log if anything failed
|
|
|
|
if t.Failed() {
|
|
|
|
gzr, err := gzip.NewReader(&logFile)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Log("Backup log contents:")
|
|
|
|
var buf bytes.Buffer
|
|
|
|
_, err = io.Copy(&buf, gzr)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, gzr.Close())
|
|
|
|
t.Log(buf.String())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if test.expectedError != nil {
|
|
|
|
assert.EqualError(t, err, test.expectedError.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockGroupBackupperFactory struct {
|
|
|
|
mock.Mock
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *mockGroupBackupperFactory) newGroupBackupper(
|
2017-11-15 02:35:02 +00:00
|
|
|
log logrus.FieldLogger,
|
2017-10-02 20:53:08 +00:00
|
|
|
backup *v1.Backup,
|
|
|
|
namespaces, resources *collections.IncludesExcludes,
|
|
|
|
labelSelector string,
|
|
|
|
dynamicFactory client.DynamicFactory,
|
|
|
|
discoveryHelper discovery.Helper,
|
|
|
|
backedUpItems map[itemKey]struct{},
|
|
|
|
cohabitatingResources map[string]*cohabitatingResource,
|
2017-11-15 02:35:02 +00:00
|
|
|
actions []resolvedAction,
|
2017-10-02 20:53:08 +00:00
|
|
|
podCommandExecutor podCommandExecutor,
|
|
|
|
tarWriter tarWriter,
|
|
|
|
resourceHooks []resourceHook,
|
2017-11-15 02:35:02 +00:00
|
|
|
snapshotService cloudprovider.SnapshotService,
|
2017-10-02 20:53:08 +00:00
|
|
|
) groupBackupper {
|
|
|
|
args := f.Called(
|
|
|
|
log,
|
|
|
|
backup,
|
|
|
|
namespaces,
|
|
|
|
resources,
|
|
|
|
labelSelector,
|
|
|
|
dynamicFactory,
|
|
|
|
discoveryHelper,
|
|
|
|
backedUpItems,
|
|
|
|
cohabitatingResources,
|
|
|
|
actions,
|
|
|
|
podCommandExecutor,
|
|
|
|
tarWriter,
|
|
|
|
resourceHooks,
|
2017-11-15 02:35:02 +00:00
|
|
|
snapshotService,
|
2017-10-02 20:53:08 +00:00
|
|
|
)
|
|
|
|
return args.Get(0).(groupBackupper)
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockGroupBackupper struct {
|
|
|
|
mock.Mock
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gb *mockGroupBackupper) backupGroup(group *metav1.APIResourceList) error {
|
|
|
|
args := gb.Called(group)
|
|
|
|
return args.Error(0)
|
|
|
|
}
|
|
|
|
|
2018-01-04 15:27:12 +00:00
|
|
|
func getAsMap(j string) (map[string]interface{}, error) {
|
|
|
|
m := make(map[string]interface{})
|
|
|
|
err := json.Unmarshal([]byte(j), &m)
|
|
|
|
return m, err
|
|
|
|
}
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2018-01-04 15:27:12 +00:00
|
|
|
func toRuntimeObject(t *testing.T, data string) runtime.Object {
|
|
|
|
o, _, err := unstructured.UnstructuredJSONScheme.Decode([]byte(data), nil, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
return o
|
|
|
|
}
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2018-01-04 15:27:12 +00:00
|
|
|
func unstructuredOrDie(data string) *unstructured.Unstructured {
|
|
|
|
o, _, err := unstructured.UnstructuredJSONScheme.Decode([]byte(data), nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
2018-01-04 15:27:12 +00:00
|
|
|
return o.(*unstructured.Unstructured)
|
|
|
|
}
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2018-01-04 15:27:12 +00:00
|
|
|
func TestGetResourceHook(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
hookSpec v1.BackupResourceHookSpec
|
|
|
|
expected resourceHook
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "PreHooks take priority over Hooks",
|
|
|
|
hookSpec: v1.BackupResourceHookSpec{
|
|
|
|
Name: "spec1",
|
|
|
|
PreHooks: []v1.BackupResourceHook{
|
|
|
|
{
|
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Container: "a",
|
|
|
|
Command: []string{"b"},
|
|
|
|
},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2018-01-04 15:27:12 +00:00
|
|
|
Hooks: []v1.BackupResourceHook{
|
|
|
|
{
|
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Container: "c",
|
|
|
|
Command: []string{"d"},
|
|
|
|
},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2018-01-04 15:27:12 +00:00
|
|
|
},
|
|
|
|
expected: resourceHook{
|
|
|
|
name: "spec1",
|
|
|
|
namespaces: collections.NewIncludesExcludes(),
|
|
|
|
resources: collections.NewIncludesExcludes(),
|
|
|
|
pre: []v1.BackupResourceHook{
|
|
|
|
{
|
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Container: "a",
|
|
|
|
Command: []string{"b"},
|
|
|
|
},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2018-01-04 15:27:12 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Use Hooks if PreHooks isn't set",
|
|
|
|
hookSpec: v1.BackupResourceHookSpec{
|
|
|
|
Name: "spec1",
|
|
|
|
Hooks: []v1.BackupResourceHook{
|
|
|
|
{
|
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Container: "a",
|
|
|
|
Command: []string{"b"},
|
|
|
|
},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2018-01-04 15:27:12 +00:00
|
|
|
},
|
|
|
|
expected: resourceHook{
|
|
|
|
name: "spec1",
|
|
|
|
namespaces: collections.NewIncludesExcludes(),
|
|
|
|
resources: collections.NewIncludesExcludes(),
|
|
|
|
pre: []v1.BackupResourceHook{
|
|
|
|
{
|
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Container: "a",
|
|
|
|
Command: []string{"b"},
|
|
|
|
},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2018-01-04 15:27:12 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Full test",
|
|
|
|
hookSpec: v1.BackupResourceHookSpec{
|
|
|
|
Name: "spec1",
|
|
|
|
IncludedNamespaces: []string{"ns1", "ns2"},
|
|
|
|
ExcludedNamespaces: []string{"ns3", "ns4"},
|
|
|
|
IncludedResources: []string{"foo", "fie"},
|
|
|
|
ExcludedResources: []string{"bar", "baz"},
|
|
|
|
PreHooks: []v1.BackupResourceHook{
|
2017-08-02 17:27:17 +00:00
|
|
|
{
|
2018-01-04 15:27:12 +00:00
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Container: "a",
|
|
|
|
Command: []string{"b"},
|
|
|
|
},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2018-01-04 15:27:12 +00:00
|
|
|
PostHooks: []v1.BackupResourceHook{
|
|
|
|
{
|
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Container: "c",
|
|
|
|
Command: []string{"d"},
|
|
|
|
},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2018-01-04 15:27:12 +00:00
|
|
|
},
|
|
|
|
expected: resourceHook{
|
|
|
|
name: "spec1",
|
|
|
|
namespaces: collections.NewIncludesExcludes().Includes("ns1", "ns2").Excludes("ns3", "ns4"),
|
|
|
|
resources: collections.NewIncludesExcludes().Includes("foodies.somegroup", "fields.somegroup").Excludes("barnacles.anothergroup", "bazaars.anothergroup"),
|
|
|
|
pre: []v1.BackupResourceHook{
|
|
|
|
{
|
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Container: "a",
|
|
|
|
Command: []string{"b"},
|
|
|
|
},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2018-01-04 15:27:12 +00:00
|
|
|
post: []v1.BackupResourceHook{
|
2017-08-02 17:27:17 +00:00
|
|
|
{
|
2018-01-04 15:27:12 +00:00
|
|
|
Exec: &v1.ExecHook{
|
|
|
|
Container: "c",
|
|
|
|
Command: []string{"d"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2018-01-04 15:27:12 +00:00
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
resources := map[schema.GroupVersionResource]schema.GroupVersionResource{
|
|
|
|
{Resource: "foo"}: {Group: "somegroup", Resource: "foodies"},
|
|
|
|
{Resource: "fie"}: {Group: "somegroup", Resource: "fields"},
|
|
|
|
{Resource: "bar"}: {Group: "anothergroup", Resource: "barnacles"},
|
|
|
|
{Resource: "baz"}: {Group: "anothergroup", Resource: "bazaars"},
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
2018-01-04 15:27:12 +00:00
|
|
|
discoveryHelper := arktest.NewFakeDiscoveryHelper(false, resources)
|
2017-10-02 20:53:08 +00:00
|
|
|
|
2018-01-04 15:27:12 +00:00
|
|
|
actual, err := getResourceHook(test.hookSpec, discoveryHelper)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, test.expected, actual)
|
|
|
|
})
|
2017-10-02 20:53:08 +00:00
|
|
|
}
|
|
|
|
}
|