Restore action for cluster/namespace role bindings (#1974)

Signed-off-by: Alexander Demichev <ademicev@redhat.com>
This commit is contained in:
Alexander Demichev
2019-10-21 23:11:26 +02:00
committed by Steve Kriss
parent f2eb072694
commit 1564317cef
6 changed files with 381 additions and 0 deletions

View File

@@ -0,0 +1 @@
Restore action for cluster/namespace role bindings

View File

@@ -55,6 +55,8 @@ func NewCommand(f client.Factory) *cobra.Command {
RegisterRestoreItemAction("velero.io/add-pvc-from-pod", newAddPVCFromPodRestoreItemAction).
RegisterRestoreItemAction("velero.io/add-pv-from-pvc", newAddPVFromPVCRestoreItemAction).
RegisterRestoreItemAction("velero.io/change-storage-class", newChangeStorageClassRestoreItemAction(f)).
RegisterRestoreItemAction("velero.io/role-bindings", newRoleBindingItemAction).
RegisterRestoreItemAction("velero.io/cluster-role-bindings", newClusterRoleBindingItemAction).
Serve()
},
}
@@ -175,3 +177,11 @@ func newChangeStorageClassRestoreItemAction(f client.Factory) veleroplugin.Handl
), nil
}
}
func newRoleBindingItemAction(logger logrus.FieldLogger) (interface{}, error) {
return restore.NewRoleBindingAction(logger), nil
}
func newClusterRoleBindingItemAction(logger logrus.FieldLogger) (interface{}, error) {
return restore.NewClusterRoleBindingAction(logger), nil
}

View File

@@ -0,0 +1,67 @@
/*
Copyright 2019 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 restore
import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
// ClusterRoleBindingAction handle namespace remappings for role bindings
type ClusterRoleBindingAction struct {
logger logrus.FieldLogger
}
func NewClusterRoleBindingAction(logger logrus.FieldLogger) *ClusterRoleBindingAction {
return &ClusterRoleBindingAction{logger: logger}
}
func (a *ClusterRoleBindingAction) AppliesTo() (velero.ResourceSelector, error) {
return velero.ResourceSelector{
IncludedResources: []string{"clusterrolebindings"},
}, nil
}
func (a *ClusterRoleBindingAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
namespaceMapping := input.Restore.Spec.NamespaceMapping
if len(namespaceMapping) == 0 {
return velero.NewRestoreItemActionExecuteOutput(&unstructured.Unstructured{Object: input.Item.UnstructuredContent()}), nil
}
clusterRoleBinding := new(rbac.ClusterRoleBinding)
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(input.Item.UnstructuredContent(), clusterRoleBinding); err != nil {
return nil, errors.WithStack(err)
}
for i, subject := range clusterRoleBinding.Subjects {
if newNamespace, ok := namespaceMapping[subject.Namespace]; ok {
clusterRoleBinding.Subjects[i].Namespace = newNamespace
}
}
res, err := runtime.DefaultUnstructuredConverter.ToUnstructured(clusterRoleBinding)
if err != nil {
return nil, errors.WithStack(err)
}
return velero.NewRestoreItemActionExecuteOutput(&unstructured.Unstructured{Object: res}), nil
}

View File

@@ -0,0 +1,118 @@
/*
Copyright 2019 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 restore
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/test"
)
func TestClusterRoleBindingActionAppliesTo(t *testing.T) {
action := NewClusterRoleBindingAction(test.NewLogger())
actual, err := action.AppliesTo()
require.NoError(t, err)
assert.Equal(t, velero.ResourceSelector{IncludedResources: []string{"clusterrolebindings"}}, actual)
}
func TestClusterRoleBindingActionExecute(t *testing.T) {
tests := []struct {
name string
namespaces []string
namespaceMapping map[string]string
expected []string
}{
{
name: "namespace mapping disabled",
namespaces: []string{"foo"},
namespaceMapping: map[string]string{},
expected: []string{"foo"},
},
{
name: "namespace mapping enabled",
namespaces: []string{"foo"},
namespaceMapping: map[string]string{"foo": "bar", "fizz": "buzz"},
expected: []string{"bar"},
},
{
name: "namespace mapping enabled, not included namespace remains unchanged",
namespaces: []string{"foo", "xyz"},
namespaceMapping: map[string]string{"foo": "bar", "fizz": "buzz"},
expected: []string{"bar", "xyz"},
},
{
name: "namespace mapping enabled, not included namespace remains unchanged",
namespaces: []string{"foo", "xyz"},
namespaceMapping: map[string]string{"a": "b", "c": "d"},
expected: []string{"foo", "xyz"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
subjects := []rbac.Subject{}
for _, ns := range tc.namespaces {
subjects = append(subjects, rbac.Subject{
Namespace: ns,
})
}
clusterRoleBinding := rbac.ClusterRoleBinding{
Subjects: subjects,
}
roleBindingUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&clusterRoleBinding)
require.NoError(t, err)
action := NewClusterRoleBindingAction(test.NewLogger())
res, err := action.Execute(&velero.RestoreItemActionExecuteInput{
Item: &unstructured.Unstructured{Object: roleBindingUnstructured},
ItemFromBackup: &unstructured.Unstructured{Object: roleBindingUnstructured},
Restore: &api.Restore{
Spec: api.RestoreSpec{
NamespaceMapping: tc.namespaceMapping,
},
},
})
require.NoError(t, err)
var resClusterRoleBinding *rbac.ClusterRoleBinding
err = runtime.DefaultUnstructuredConverter.FromUnstructured(res.UpdatedItem.UnstructuredContent(), &resClusterRoleBinding)
require.NoError(t, err)
actual := []string{}
for _, subject := range resClusterRoleBinding.Subjects {
actual = append(actual, subject.Namespace)
}
sort.Strings(tc.expected)
sort.Strings(actual)
assert.Equal(t, tc.expected, actual)
})
}
}

View File

@@ -0,0 +1,67 @@
/*
Copyright 2019 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 restore
import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
// RoleBindingAction handle namespace remappings for role bindings
type RoleBindingAction struct {
logger logrus.FieldLogger
}
func NewRoleBindingAction(logger logrus.FieldLogger) *RoleBindingAction {
return &RoleBindingAction{logger: logger}
}
func (a *RoleBindingAction) AppliesTo() (velero.ResourceSelector, error) {
return velero.ResourceSelector{
IncludedResources: []string{"rolebindings"},
}, nil
}
func (a *RoleBindingAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
namespaceMapping := input.Restore.Spec.NamespaceMapping
if len(namespaceMapping) == 0 {
return velero.NewRestoreItemActionExecuteOutput(&unstructured.Unstructured{Object: input.Item.UnstructuredContent()}), nil
}
roleBinding := new(rbac.RoleBinding)
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(input.Item.UnstructuredContent(), roleBinding); err != nil {
return nil, errors.WithStack(err)
}
for i, subject := range roleBinding.Subjects {
if newNamespace, ok := namespaceMapping[subject.Namespace]; ok {
roleBinding.Subjects[i].Namespace = newNamespace
}
}
res, err := runtime.DefaultUnstructuredConverter.ToUnstructured(roleBinding)
if err != nil {
return nil, errors.WithStack(err)
}
return velero.NewRestoreItemActionExecuteOutput(&unstructured.Unstructured{Object: res}), nil
}

View File

@@ -0,0 +1,118 @@
/*
Copyright 2019 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 restore
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/test"
)
func TestRoleBindingActionAppliesTo(t *testing.T) {
action := NewRoleBindingAction(test.NewLogger())
actual, err := action.AppliesTo()
require.NoError(t, err)
assert.Equal(t, velero.ResourceSelector{IncludedResources: []string{"rolebindings"}}, actual)
}
func TestRoleBindingActionExecute(t *testing.T) {
tests := []struct {
name string
namespaces []string
namespaceMapping map[string]string
expected []string
}{
{
name: "namespace mapping disabled",
namespaces: []string{"foo"},
namespaceMapping: map[string]string{},
expected: []string{"foo"},
},
{
name: "namespace mapping enabled",
namespaces: []string{"foo"},
namespaceMapping: map[string]string{"foo": "bar", "fizz": "buzz"},
expected: []string{"bar"},
},
{
name: "namespace mapping enabled, not included namespace remains unchanged",
namespaces: []string{"foo", "xyz"},
namespaceMapping: map[string]string{"foo": "bar", "fizz": "buzz"},
expected: []string{"bar", "xyz"},
},
{
name: "namespace mapping enabled, not included namespace remains unchanged",
namespaces: []string{"foo", "xyz"},
namespaceMapping: map[string]string{"a": "b", "c": "d"},
expected: []string{"foo", "xyz"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
subjects := []rbac.Subject{}
for _, ns := range tc.namespaces {
subjects = append(subjects, rbac.Subject{
Namespace: ns,
})
}
roleBinding := rbac.RoleBinding{
Subjects: subjects,
}
roleBindingUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&roleBinding)
require.NoError(t, err)
action := NewRoleBindingAction(test.NewLogger())
res, err := action.Execute(&velero.RestoreItemActionExecuteInput{
Item: &unstructured.Unstructured{Object: roleBindingUnstructured},
ItemFromBackup: &unstructured.Unstructured{Object: roleBindingUnstructured},
Restore: &api.Restore{
Spec: api.RestoreSpec{
NamespaceMapping: tc.namespaceMapping,
},
},
})
require.NoError(t, err)
var resRoleBinding *rbac.RoleBinding
err = runtime.DefaultUnstructuredConverter.FromUnstructured(res.UpdatedItem.UnstructuredContent(), &resRoleBinding)
require.NoError(t, err)
actual := []string{}
for _, subject := range resRoleBinding.Subjects {
actual = append(actual, subject.Namespace)
}
sort.Strings(tc.expected)
sort.Strings(actual)
assert.Equal(t, tc.expected, actual)
})
}
}