mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-26 01:44:19 +00:00
Initial commit
Signed-off-by: Andy Goldstein <andy.goldstein@gmail.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
type jobRestorer struct{}
|
||||
|
||||
var _ ResourceRestorer = &jobRestorer{}
|
||||
|
||||
func NewJobRestorer() ResourceRestorer {
|
||||
return &jobRestorer{}
|
||||
}
|
||||
|
||||
func (r *jobRestorer) Handles(obj runtime.Unstructured, restore *api.Restore) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *jobRestorer) Prepare(obj runtime.Unstructured, restore *api.Restore, backup *api.Backup) (runtime.Unstructured, error) {
|
||||
glog.V(4).Infof("resetting metadata and status")
|
||||
_, err := resetMetadataAndStatus(obj, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("getting spec.selector.matchLabels")
|
||||
matchLabels, err := collections.GetMap(obj.UnstructuredContent(), "spec.selector.matchLabels")
|
||||
if err != nil {
|
||||
glog.V(4).Infof("unable to get spec.selector.matchLabels: %v", err)
|
||||
} else {
|
||||
delete(matchLabels, "controller-uid")
|
||||
}
|
||||
|
||||
templateLabels, err := collections.GetMap(obj.UnstructuredContent(), "spec.template.metadata.labels")
|
||||
if err != nil {
|
||||
glog.V(4).Infof("unable to get spec.template.metadata.labels: %v", err)
|
||||
} else {
|
||||
delete(templateLabels, "controller-uid")
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (r *jobRestorer) Wait() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *jobRestorer) Ready(obj runtime.Unstructured) bool {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestJobRestorerPrepare(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Unstructured
|
||||
expectedErr bool
|
||||
expectedRes runtime.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "no metadata should error",
|
||||
obj: NewTestUnstructured().Unstructured,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing spec.selector and/or spec.template should not error",
|
||||
obj: NewTestUnstructured().WithName("job-1").
|
||||
WithSpec().
|
||||
Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("job-1").
|
||||
WithSpec().
|
||||
Unstructured,
|
||||
},
|
||||
{
|
||||
name: "missing spec.selector.matchLabels should not error",
|
||||
obj: NewTestUnstructured().WithName("job-1").
|
||||
WithSpecField("selector", map[string]interface{}{}).
|
||||
Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("job-1").
|
||||
WithSpecField("selector", map[string]interface{}{}).
|
||||
Unstructured,
|
||||
},
|
||||
{
|
||||
name: "spec.selector.matchLabels[controller-uid] is removed",
|
||||
obj: NewTestUnstructured().WithName("job-1").
|
||||
WithSpecField("selector", map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"controller-uid": "foo",
|
||||
"hello": "world",
|
||||
},
|
||||
}).
|
||||
Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("job-1").
|
||||
WithSpecField("selector", map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"hello": "world",
|
||||
},
|
||||
}).
|
||||
Unstructured,
|
||||
},
|
||||
{
|
||||
name: "missing spec.template.metadata should not error",
|
||||
obj: NewTestUnstructured().WithName("job-1").
|
||||
WithSpecField("template", map[string]interface{}{}).
|
||||
Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("job-1").
|
||||
WithSpecField("template", map[string]interface{}{}).
|
||||
Unstructured,
|
||||
},
|
||||
{
|
||||
name: "missing spec.template.metadata.labels should not error",
|
||||
obj: NewTestUnstructured().WithName("job-1").
|
||||
WithSpecField("template", map[string]interface{}{
|
||||
"metadata": map[string]interface{}{},
|
||||
}).
|
||||
Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("job-1").
|
||||
WithSpecField("template", map[string]interface{}{
|
||||
"metadata": map[string]interface{}{},
|
||||
}).
|
||||
Unstructured,
|
||||
},
|
||||
{
|
||||
name: "spec.template.metadata.labels[controller-uid] is removed",
|
||||
obj: NewTestUnstructured().WithName("job-1").
|
||||
WithSpecField("template", map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"controller-uid": "foo",
|
||||
"hello": "world",
|
||||
},
|
||||
},
|
||||
}).
|
||||
Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("job-1").
|
||||
WithSpecField("template", map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"hello": "world",
|
||||
},
|
||||
},
|
||||
}).
|
||||
Unstructured,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
restorer := NewJobRestorer()
|
||||
|
||||
res, err := restorer.Prepare(test.obj, nil, nil)
|
||||
|
||||
if assert.Equal(t, test.expectedErr, err != nil) {
|
||||
assert.Equal(t, test.expectedRes, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
type namespaceRestorer struct{}
|
||||
|
||||
var _ ResourceRestorer = &namespaceRestorer{}
|
||||
|
||||
func NewNamespaceRestorer() ResourceRestorer {
|
||||
return &namespaceRestorer{}
|
||||
}
|
||||
|
||||
func (nsr *namespaceRestorer) Handles(obj runtime.Unstructured, restore *api.Restore) bool {
|
||||
nsName, err := collections.GetString(obj.UnstructuredContent(), "metadata.name")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, restorableNS := range restore.Spec.Namespaces {
|
||||
if restorableNS == nsName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (nsr *namespaceRestorer) Prepare(obj runtime.Unstructured, restore *api.Restore, backup *api.Backup) (runtime.Unstructured, error) {
|
||||
updated, err := resetMetadataAndStatus(obj, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadata, err := collections.GetMap(obj.UnstructuredContent(), "metadata")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentName, err := collections.GetString(obj.UnstructuredContent(), "metadata.name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if newName, mapped := restore.Spec.NamespaceMapping[currentName]; mapped {
|
||||
metadata["name"] = newName
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (nsr *namespaceRestorer) Wait() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (nsr *namespaceRestorer) Ready(obj runtime.Unstructured) bool {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Unstructured
|
||||
restore *api.Restore
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
name: "restorable NS",
|
||||
obj: NewTestUnstructured().WithName("ns-1").Unstructured,
|
||||
restore: newTestRestore().WithRestorableNamespace("ns-1").Restore,
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
name: "non-restorable NS",
|
||||
obj: NewTestUnstructured().WithName("ns-1").Unstructured,
|
||||
restore: newTestRestore().WithRestorableNamespace("ns-2").Restore,
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "namespace obj doesn't have name",
|
||||
obj: NewTestUnstructured().WithMetadata().Unstructured,
|
||||
restore: newTestRestore().WithRestorableNamespace("ns-1").Restore,
|
||||
expect: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
restorer := NewNamespaceRestorer()
|
||||
assert.Equal(t, test.expect, restorer.Handles(test.obj, test.restore))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepare(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Unstructured
|
||||
restore *api.Restore
|
||||
expectedErr bool
|
||||
expectedRes runtime.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "standard non-mapped namespace",
|
||||
obj: NewTestUnstructured().WithStatus().WithName("ns-1").Unstructured,
|
||||
restore: newTestRestore().Restore,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("ns-1").Unstructured,
|
||||
},
|
||||
{
|
||||
name: "standard mapped namespace",
|
||||
obj: NewTestUnstructured().WithStatus().WithName("ns-1").Unstructured,
|
||||
restore: newTestRestore().WithMappedNamespace("ns-1", "ns-2").Restore,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("ns-2").Unstructured,
|
||||
},
|
||||
{
|
||||
name: "object without name results in error",
|
||||
obj: NewTestUnstructured().WithMetadata().WithStatus().Unstructured,
|
||||
restore: newTestRestore().Restore,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "annotations are kept",
|
||||
obj: NewTestUnstructured().WithName("ns-1").WithAnnotations().Unstructured,
|
||||
restore: newTestRestore().Restore,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("ns-1").WithAnnotations().Unstructured,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
restorer := NewNamespaceRestorer()
|
||||
|
||||
res, err := restorer.Prepare(test.obj, test.restore, nil)
|
||||
|
||||
if assert.Equal(t, test.expectedErr, err != nil) {
|
||||
assert.Equal(t, test.expectedRes, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testRestore struct {
|
||||
*api.Restore
|
||||
}
|
||||
|
||||
func newTestRestore() *testRestore {
|
||||
return &testRestore{
|
||||
Restore: &api.Restore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: api.DefaultNamespace,
|
||||
},
|
||||
Spec: api.RestoreSpec{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *testRestore) WithRestorableNamespace(namespace string) *testRestore {
|
||||
r.Spec.Namespaces = append(r.Spec.Namespaces, namespace)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *testRestore) WithMappedNamespace(from string, to string) *testRestore {
|
||||
if r.Spec.NamespaceMapping == nil {
|
||||
r.Spec.NamespaceMapping = make(map[string]string)
|
||||
}
|
||||
r.Spec.NamespaceMapping[from] = to
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *testRestore) WithRestorePVs(restorePVs bool) *testRestore {
|
||||
r.Spec.RestorePVs = restorePVs
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
type podRestorer struct{}
|
||||
|
||||
var _ ResourceRestorer = &podRestorer{}
|
||||
|
||||
func NewPodRestorer() ResourceRestorer {
|
||||
return &podRestorer{}
|
||||
}
|
||||
|
||||
func (nsr *podRestorer) Handles(obj runtime.Unstructured, restore *api.Restore) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
defaultTokenRegex = regexp.MustCompile("default-token-.*")
|
||||
)
|
||||
|
||||
func (nsr *podRestorer) Prepare(obj runtime.Unstructured, restore *api.Restore, backup *api.Backup) (runtime.Unstructured, error) {
|
||||
glog.V(4).Infof("resetting metadata and status")
|
||||
_, err := resetMetadataAndStatus(obj, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("getting spec")
|
||||
spec, err := collections.GetMap(obj.UnstructuredContent(), "spec")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("deleting spec.NodeName")
|
||||
delete(spec, "nodeName")
|
||||
|
||||
newVolumes := make([]interface{}, 0)
|
||||
glog.V(4).Infof("iterating over volumes")
|
||||
err = collections.ForEach(spec, "volumes", func(volume map[string]interface{}) error {
|
||||
name, err := collections.GetString(volume, "name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("checking volume with name %q", name)
|
||||
|
||||
if !defaultTokenRegex.MatchString(name) {
|
||||
glog.V(4).Infof("preserving volume")
|
||||
newVolumes = append(newVolumes, volume)
|
||||
} else {
|
||||
glog.V(4).Infof("excluding volume")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("setting spec.volumes")
|
||||
spec["volumes"] = newVolumes
|
||||
|
||||
glog.V(4).Infof("iterating over containers")
|
||||
err = collections.ForEach(spec, "containers", func(container map[string]interface{}) error {
|
||||
var newVolumeMounts []interface{}
|
||||
err := collections.ForEach(container, "volumeMounts", func(volumeMount map[string]interface{}) error {
|
||||
name, err := collections.GetString(volumeMount, "name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("checking volumeMount with name %q", name)
|
||||
|
||||
if !defaultTokenRegex.MatchString(name) {
|
||||
glog.V(4).Infof("preserving volumeMount")
|
||||
newVolumeMounts = append(newVolumeMounts, volumeMount)
|
||||
} else {
|
||||
glog.V(4).Infof("excluding volumeMount")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container["volumeMounts"] = newVolumeMounts
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (nsr *podRestorer) Wait() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (nsr *podRestorer) Ready(obj runtime.Unstructured) bool {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestPodRestorerPrepare(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Unstructured
|
||||
expectedErr bool
|
||||
expectedRes runtime.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "no spec should error",
|
||||
obj: NewTestUnstructured().WithName("pod-1").Unstructured,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "nodeName (only) should be deleted from spec",
|
||||
obj: NewTestUnstructured().WithName("pod-1").WithSpec("nodeName", "foo").
|
||||
WithSpecField("volumes", []interface{}{}).
|
||||
WithSpecField("containers", []interface{}{}).
|
||||
Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("pod-1").WithSpec("foo").
|
||||
WithSpecField("volumes", []interface{}{}).
|
||||
WithSpecField("containers", []interface{}{}).
|
||||
Unstructured,
|
||||
},
|
||||
{
|
||||
name: "volumes matching default-token regex should be deleted",
|
||||
obj: NewTestUnstructured().WithName("pod-1").
|
||||
WithSpecField("volumes", []interface{}{
|
||||
map[string]interface{}{"name": "foo"},
|
||||
map[string]interface{}{"name": "default-token-foo"},
|
||||
}).WithSpecField("containers", []interface{}{}).Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("pod-1").
|
||||
WithSpecField("volumes", []interface{}{
|
||||
map[string]interface{}{"name": "foo"},
|
||||
}).WithSpecField("containers", []interface{}{}).Unstructured,
|
||||
},
|
||||
{
|
||||
name: "container volumeMounts matching default-token regex should be deleted",
|
||||
obj: NewTestUnstructured().WithName("svc-1").
|
||||
WithSpecField("volumes", []interface{}{}).
|
||||
WithSpecField("containers", []interface{}{
|
||||
map[string]interface{}{
|
||||
"volumeMounts": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "foo",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "default-token-foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
}).
|
||||
Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("svc-1").
|
||||
WithSpecField("volumes", []interface{}{}).
|
||||
WithSpecField("containers", []interface{}{
|
||||
map[string]interface{}{
|
||||
"volumeMounts": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
}).
|
||||
Unstructured,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
restorer := NewPodRestorer()
|
||||
|
||||
res, err := restorer.Prepare(test.obj, nil, nil)
|
||||
|
||||
if assert.Equal(t, test.expectedErr, err != nil) {
|
||||
assert.Equal(t, test.expectedRes, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
type persistentVolumeRestorer struct {
|
||||
snapshotService cloudprovider.SnapshotService
|
||||
}
|
||||
|
||||
var _ ResourceRestorer = &persistentVolumeRestorer{}
|
||||
|
||||
func NewPersistentVolumeRestorer(snapshotService cloudprovider.SnapshotService) ResourceRestorer {
|
||||
return &persistentVolumeRestorer{
|
||||
snapshotService: snapshotService,
|
||||
}
|
||||
}
|
||||
|
||||
func (sr *persistentVolumeRestorer) Handles(obj runtime.Unstructured, restore *api.Restore) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (sr *persistentVolumeRestorer) Prepare(obj runtime.Unstructured, restore *api.Restore, backup *api.Backup) (runtime.Unstructured, error) {
|
||||
if _, err := resetMetadataAndStatus(obj, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spec, err := collections.GetMap(obj.UnstructuredContent(), "spec")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(spec, "claimRef")
|
||||
delete(spec, "storageClassName")
|
||||
|
||||
if restore.Spec.RestorePVs {
|
||||
volumeID, err := sr.restoreVolume(obj.UnstructuredContent(), restore, backup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := setVolumeID(spec, volumeID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (sr *persistentVolumeRestorer) Wait() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (sr *persistentVolumeRestorer) Ready(obj runtime.Unstructured) bool {
|
||||
phase, err := collections.GetString(obj.UnstructuredContent(), "status.phase")
|
||||
|
||||
return err == nil && phase == "Available"
|
||||
}
|
||||
|
||||
func setVolumeID(spec map[string]interface{}, volumeID string) error {
|
||||
if pvSource, found := spec["awsElasticBlockStore"]; found {
|
||||
pvSourceObj := pvSource.(map[string]interface{})
|
||||
pvSourceObj["volumeID"] = volumeID
|
||||
return nil
|
||||
} else if pvSource, found := spec["gcePersistentDisk"]; found {
|
||||
pvSourceObj := pvSource.(map[string]interface{})
|
||||
pvSourceObj["pdName"] = volumeID
|
||||
return nil
|
||||
} else if pvSource, found := spec["azureDisk"]; found {
|
||||
pvSourceObj := pvSource.(map[string]interface{})
|
||||
pvSourceObj["diskName"] = volumeID
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("persistent volume source is not compatible")
|
||||
}
|
||||
|
||||
func (sr *persistentVolumeRestorer) restoreVolume(item map[string]interface{}, restore *api.Restore, backup *api.Backup) (string, error) {
|
||||
pvName, err := collections.GetString(item, "metadata.name")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if backup.Status.VolumeBackups == nil {
|
||||
return "", fmt.Errorf("VolumeBackups map not found for persistent volume %s", pvName)
|
||||
}
|
||||
|
||||
backupInfo, found := backup.Status.VolumeBackups[pvName]
|
||||
if !found {
|
||||
return "", fmt.Errorf("BackupInfo not found for PersistentVolume %s", pvName)
|
||||
}
|
||||
|
||||
return sr.snapshotService.CreateVolumeFromSnapshot(backupInfo.SnapshotID, backupInfo.Type, backupInfo.Iops)
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
. "github.com/heptio/ark/pkg/util/test"
|
||||
)
|
||||
|
||||
func TestPVRestorerPrepare(t *testing.T) {
|
||||
iops := 1000
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Unstructured
|
||||
restore *api.Restore
|
||||
backup *api.Backup
|
||||
volumeMap map[api.VolumeBackupInfo]string
|
||||
expectedErr bool
|
||||
expectedRes runtime.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "no name should error",
|
||||
obj: NewTestUnstructured().WithMetadata().Unstructured,
|
||||
restore: newTestRestore().Restore,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "no spec should error",
|
||||
obj: NewTestUnstructured().WithName("pv-1").Unstructured,
|
||||
restore: newTestRestore().Restore,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "when RestorePVs=false, should not error if there is no PV->BackupInfo map",
|
||||
obj: NewTestUnstructured().WithName("pv-1").WithSpec().Unstructured,
|
||||
restore: newTestRestore().WithRestorePVs(false).Restore,
|
||||
backup: &api.Backup{Status: api.BackupStatus{}},
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpec().Unstructured,
|
||||
},
|
||||
{
|
||||
name: "when RestorePVs=true, error if there is no PV->BackupInfo map",
|
||||
obj: NewTestUnstructured().WithName("pv-1").WithSpec().Unstructured,
|
||||
restore: newTestRestore().WithRestorePVs(true).Restore,
|
||||
backup: &api.Backup{Status: api.BackupStatus{}},
|
||||
expectedErr: true,
|
||||
expectedRes: nil,
|
||||
},
|
||||
{
|
||||
name: "claimRef and storageClassName (only) should be cleared from spec",
|
||||
obj: NewTestUnstructured().
|
||||
WithName("pv-1").
|
||||
WithSpecField("claimRef", "foo").
|
||||
WithSpecField("storageClassName", "foo").
|
||||
WithSpecField("foo", "bar").
|
||||
Unstructured,
|
||||
restore: newTestRestore().WithRestorePVs(false).Restore,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().
|
||||
WithName("pv-1").
|
||||
WithSpecField("foo", "bar").
|
||||
Unstructured,
|
||||
},
|
||||
{
|
||||
name: "when RestorePVs=true, AWS volume ID should be set correctly",
|
||||
obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured,
|
||||
restore: newTestRestore().WithRestorePVs(true).Restore,
|
||||
backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": &api.VolumeBackupInfo{SnapshotID: "snap-1"}}}},
|
||||
volumeMap: map[api.VolumeBackupInfo]string{api.VolumeBackupInfo{SnapshotID: "snap-1"}: "volume-1"},
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", map[string]interface{}{"volumeID": "volume-1"}).Unstructured,
|
||||
},
|
||||
{
|
||||
name: "when RestorePVs=true, GCE pdName should be set correctly",
|
||||
obj: NewTestUnstructured().WithName("pv-1").WithSpecField("gcePersistentDisk", make(map[string]interface{})).Unstructured,
|
||||
restore: newTestRestore().WithRestorePVs(true).Restore,
|
||||
backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": &api.VolumeBackupInfo{SnapshotID: "snap-1"}}}},
|
||||
volumeMap: map[api.VolumeBackupInfo]string{api.VolumeBackupInfo{SnapshotID: "snap-1"}: "volume-1"},
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("gcePersistentDisk", map[string]interface{}{"pdName": "volume-1"}).Unstructured,
|
||||
},
|
||||
{
|
||||
name: "when RestorePVs=true, Azure pdName should be set correctly",
|
||||
obj: NewTestUnstructured().WithName("pv-1").WithSpecField("azureDisk", make(map[string]interface{})).Unstructured,
|
||||
restore: newTestRestore().WithRestorePVs(true).Restore,
|
||||
backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": &api.VolumeBackupInfo{SnapshotID: "snap-1"}}}},
|
||||
volumeMap: map[api.VolumeBackupInfo]string{api.VolumeBackupInfo{SnapshotID: "snap-1"}: "volume-1"},
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("azureDisk", map[string]interface{}{"diskName": "volume-1"}).Unstructured,
|
||||
},
|
||||
{
|
||||
name: "when RestorePVs=true, unsupported PV source should cause error",
|
||||
obj: NewTestUnstructured().WithName("pv-1").WithSpecField("unsupportedPVSource", make(map[string]interface{})).Unstructured,
|
||||
restore: newTestRestore().WithRestorePVs(true).Restore,
|
||||
backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": &api.VolumeBackupInfo{SnapshotID: "snap-1"}}}},
|
||||
volumeMap: map[api.VolumeBackupInfo]string{api.VolumeBackupInfo{SnapshotID: "snap-1"}: "volume-1"},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "volume type and IOPS are correctly passed to CreateVolume",
|
||||
obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured,
|
||||
restore: newTestRestore().WithRestorePVs(true).Restore,
|
||||
backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": &api.VolumeBackupInfo{SnapshotID: "snap-1", Type: "gp", Iops: &iops}}}},
|
||||
volumeMap: map[api.VolumeBackupInfo]string{api.VolumeBackupInfo{SnapshotID: "snap-1", Type: "gp", Iops: &iops}: "volume-1"},
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", map[string]interface{}{"volumeID": "volume-1"}).Unstructured,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
snapService := &FakeSnapshotService{RestorableVolumes: test.volumeMap}
|
||||
restorer := NewPersistentVolumeRestorer(snapService)
|
||||
|
||||
res, err := restorer.Prepare(test.obj, test.restore, test.backup)
|
||||
|
||||
if assert.Equal(t, test.expectedErr, err != nil) {
|
||||
assert.Equal(t, test.expectedRes, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPVRestorerReady(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj *unstructured.Unstructured
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "no status returns not ready",
|
||||
obj: NewTestUnstructured().Unstructured,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no status.phase returns not ready",
|
||||
obj: NewTestUnstructured().WithStatus().Unstructured,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty status.phase returns not ready",
|
||||
obj: NewTestUnstructured().WithStatusField("phase", "").Unstructured,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "non-Available status.phase returns not ready",
|
||||
obj: NewTestUnstructured().WithStatusField("phase", "foo").Unstructured,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Available status.phase returns ready",
|
||||
obj: NewTestUnstructured().WithStatusField("phase", "Available").Unstructured,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
restorer := NewPersistentVolumeRestorer(nil)
|
||||
|
||||
assert.Equal(t, test.expected, restorer.Ready(test.obj))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
type persistentVolumeClaimRestorer struct{}
|
||||
|
||||
var _ ResourceRestorer = &persistentVolumeClaimRestorer{}
|
||||
|
||||
func NewPersistentVolumeClaimRestorer() ResourceRestorer {
|
||||
return &persistentVolumeClaimRestorer{}
|
||||
}
|
||||
|
||||
func (sr *persistentVolumeClaimRestorer) Handles(obj runtime.Unstructured, restore *api.Restore) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (sr *persistentVolumeClaimRestorer) Prepare(obj runtime.Unstructured, restore *api.Restore, backup *api.Backup) (runtime.Unstructured, error) {
|
||||
return resetMetadataAndStatus(obj, true)
|
||||
}
|
||||
|
||||
func (sr *persistentVolumeClaimRestorer) Wait() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (sr *persistentVolumeClaimRestorer) Ready(obj runtime.Unstructured) bool {
|
||||
phase, err := collections.GetString(obj.UnstructuredContent(), "status.phase")
|
||||
|
||||
return err == nil && phase == "Bound"
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func TestPVCRestorerReady(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj *unstructured.Unstructured
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "no status returns not ready",
|
||||
obj: NewTestUnstructured().Unstructured,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no status.phase returns not ready",
|
||||
obj: NewTestUnstructured().WithStatus().Unstructured,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty status.phase returns not ready",
|
||||
obj: NewTestUnstructured().WithStatusField("phase", "").Unstructured,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "non-Available status.phase returns not ready",
|
||||
obj: NewTestUnstructured().WithStatusField("phase", "foo").Unstructured,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Bound status.phase returns ready",
|
||||
obj: NewTestUnstructured().WithStatusField("phase", "Bound").Unstructured,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
restorer := NewPersistentVolumeClaimRestorer()
|
||||
|
||||
assert.Equal(t, test.expected, restorer.Ready(test.obj))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
// ResourceRestorer exposes the operations necessary to prepare Kubernetes resources
|
||||
// for restore and confirm their readiness following restoration via Ark.
|
||||
type ResourceRestorer interface {
|
||||
// Handles returns true if the Restorer should restore this object.
|
||||
Handles(obj runtime.Unstructured, restore *api.Restore) bool
|
||||
|
||||
// Prepare gets an item ready to be restored
|
||||
Prepare(obj runtime.Unstructured, restore *api.Restore, backup *api.Backup) (runtime.Unstructured, error)
|
||||
|
||||
// Wait returns true if restoration should wait for all of this restorer's resources to be ready before moving on to the next restorer.
|
||||
Wait() bool
|
||||
|
||||
// Ready returns true if the given item is considered ready by the system. Only used if Wait() returns true.
|
||||
Ready(obj runtime.Unstructured) bool
|
||||
}
|
||||
|
||||
func resetMetadataAndStatus(obj runtime.Unstructured, keepAnnotations bool) (runtime.Unstructured, error) {
|
||||
metadata, err := collections.GetMap(obj.UnstructuredContent(), "metadata")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k := range metadata {
|
||||
if k != "name" && k != "namespace" && k != "labels" && (!keepAnnotations || k != "annotations") {
|
||||
delete(metadata, k)
|
||||
}
|
||||
}
|
||||
|
||||
delete(obj.UnstructuredContent(), "status")
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
var _ ResourceRestorer = &basicRestorer{}
|
||||
|
||||
type basicRestorer struct {
|
||||
saveAnnotations bool
|
||||
}
|
||||
|
||||
func (br *basicRestorer) Handles(obj runtime.Unstructured, restore *api.Restore) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (br *basicRestorer) Prepare(obj runtime.Unstructured, restore *api.Restore, backup *api.Backup) (runtime.Unstructured, error) {
|
||||
return resetMetadataAndStatus(obj, br.saveAnnotations)
|
||||
}
|
||||
|
||||
func (br *basicRestorer) Wait() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (br *basicRestorer) Ready(obj runtime.Unstructured) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func NewBasicRestorer(saveAnnotations bool) ResourceRestorer {
|
||||
return &basicRestorer{saveAnnotations: saveAnnotations}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestResetMetadataAndStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Unstructured
|
||||
keepAnnotations bool
|
||||
expectedErr bool
|
||||
expectedRes runtime.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "no metadata causes error",
|
||||
obj: NewTestUnstructured(),
|
||||
keepAnnotations: false,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "don't keep annotations",
|
||||
obj: NewTestUnstructured().WithMetadata("name", "namespace", "labels", "annotations").Unstructured,
|
||||
keepAnnotations: false,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithMetadata("name", "namespace", "labels").Unstructured,
|
||||
},
|
||||
{
|
||||
name: "keep annotations",
|
||||
obj: NewTestUnstructured().WithMetadata("name", "namespace", "labels", "annotations").Unstructured,
|
||||
keepAnnotations: true,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithMetadata("name", "namespace", "labels", "annotations").Unstructured,
|
||||
},
|
||||
{
|
||||
name: "don't keep extraneous metadata",
|
||||
obj: NewTestUnstructured().WithMetadata("foo").Unstructured,
|
||||
keepAnnotations: false,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithMetadata().Unstructured,
|
||||
},
|
||||
{
|
||||
name: "don't keep status",
|
||||
obj: NewTestUnstructured().WithMetadata().WithStatus().Unstructured,
|
||||
keepAnnotations: false,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithMetadata().Unstructured,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := resetMetadataAndStatus(test.obj, test.keepAnnotations)
|
||||
|
||||
if assert.Equal(t, test.expectedErr, err != nil) {
|
||||
assert.Equal(t, test.expectedRes, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testUnstructured struct {
|
||||
*unstructured.Unstructured
|
||||
}
|
||||
|
||||
func NewTestUnstructured() *testUnstructured {
|
||||
obj := &testUnstructured{
|
||||
Unstructured: &unstructured.Unstructured{
|
||||
Object: make(map[string]interface{}),
|
||||
},
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func (obj *testUnstructured) WithMetadata(fields ...string) *testUnstructured {
|
||||
return obj.withMap("metadata", fields...)
|
||||
}
|
||||
|
||||
func (obj *testUnstructured) WithSpec(fields ...string) *testUnstructured {
|
||||
return obj.withMap("spec", fields...)
|
||||
}
|
||||
|
||||
func (obj *testUnstructured) WithStatus(fields ...string) *testUnstructured {
|
||||
return obj.withMap("status", fields...)
|
||||
}
|
||||
|
||||
func (obj *testUnstructured) WithMetadataField(field string, value interface{}) *testUnstructured {
|
||||
return obj.withMapEntry("metadata", field, value)
|
||||
}
|
||||
|
||||
func (obj *testUnstructured) WithSpecField(field string, value interface{}) *testUnstructured {
|
||||
return obj.withMapEntry("spec", field, value)
|
||||
}
|
||||
|
||||
func (obj *testUnstructured) WithStatusField(field string, value interface{}) *testUnstructured {
|
||||
return obj.withMapEntry("status", field, value)
|
||||
}
|
||||
|
||||
func (obj *testUnstructured) WithAnnotations(fields ...string) *testUnstructured {
|
||||
annotations := make(map[string]interface{})
|
||||
for _, field := range fields {
|
||||
annotations[field] = "foo"
|
||||
}
|
||||
|
||||
obj = obj.WithMetadataField("annotations", annotations)
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func (obj *testUnstructured) WithName(name string) *testUnstructured {
|
||||
return obj.WithMetadataField("name", name)
|
||||
}
|
||||
|
||||
func (obj *testUnstructured) withMap(name string, fields ...string) *testUnstructured {
|
||||
m := make(map[string]interface{})
|
||||
obj.Object[name] = m
|
||||
|
||||
for _, field := range fields {
|
||||
m[field] = "foo"
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func (obj *testUnstructured) withMapEntry(mapName, field string, value interface{}) *testUnstructured {
|
||||
var m map[string]interface{}
|
||||
|
||||
if res, ok := obj.Unstructured.Object[mapName]; !ok {
|
||||
m = make(map[string]interface{})
|
||||
obj.Unstructured.Object[mapName] = m
|
||||
} else {
|
||||
m = res.(map[string]interface{})
|
||||
}
|
||||
|
||||
m[field] = value
|
||||
|
||||
return obj
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
type serviceRestorer struct{}
|
||||
|
||||
var _ ResourceRestorer = &serviceRestorer{}
|
||||
|
||||
func NewServiceRestorer() ResourceRestorer {
|
||||
return &serviceRestorer{}
|
||||
}
|
||||
|
||||
func (sr *serviceRestorer) Handles(obj runtime.Unstructured, restore *api.Restore) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (sr *serviceRestorer) Prepare(obj runtime.Unstructured, restore *api.Restore, backup *api.Backup) (runtime.Unstructured, error) {
|
||||
if _, err := resetMetadataAndStatus(obj, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spec, err := collections.GetMap(obj.UnstructuredContent(), "spec")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(spec, "clusterIP")
|
||||
|
||||
ports, err := collections.GetSlice(obj.UnstructuredContent(), "spec.ports")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, port := range ports {
|
||||
p := port.(map[string]interface{})
|
||||
delete(p, "nodePort")
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (sr *serviceRestorer) Wait() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (sr *serviceRestorer) Ready(obj runtime.Unstructured) bool {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright 2017 Heptio Inc.
|
||||
|
||||
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 restorers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestServiceRestorerPrepare(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Unstructured
|
||||
expectedErr bool
|
||||
expectedRes runtime.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "no spec should error",
|
||||
obj: NewTestUnstructured().WithName("svc-1").Unstructured,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "clusterIP (only) should be deleted from spec",
|
||||
obj: NewTestUnstructured().WithName("svc-1").WithSpec("clusterIP", "foo").WithSpecField("ports", []interface{}{}).Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("svc-1").WithSpec("foo").WithSpecField("ports", []interface{}{}).Unstructured,
|
||||
},
|
||||
{
|
||||
name: "nodePort (only) should be deleted from all spec.ports",
|
||||
obj: NewTestUnstructured().WithName("svc-1").
|
||||
WithSpecField("ports", []interface{}{
|
||||
map[string]interface{}{"nodePort": ""},
|
||||
map[string]interface{}{"nodePort": "", "foo": "bar"},
|
||||
}).Unstructured,
|
||||
expectedErr: false,
|
||||
expectedRes: NewTestUnstructured().WithName("svc-1").
|
||||
WithSpecField("ports", []interface{}{
|
||||
map[string]interface{}{},
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
}).Unstructured,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
restorer := NewServiceRestorer()
|
||||
|
||||
res, err := restorer.Prepare(test.obj, nil, nil)
|
||||
|
||||
if assert.Equal(t, test.expectedErr, err != nil) {
|
||||
assert.Equal(t, test.expectedRes, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user