mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-08 06:15:40 +00:00
Restore action for cluster/namespace role bindings (#1974)
Signed-off-by: Alexander Demichev <ademicev@redhat.com>
This commit is contained in:
committed by
Steve Kriss
parent
f2eb072694
commit
1564317cef
1
changelogs/unreleased/1974-alexander-demichev
Normal file
1
changelogs/unreleased/1974-alexander-demichev
Normal file
@@ -0,0 +1 @@
|
||||
Restore action for cluster/namespace role bindings
|
||||
@@ -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
|
||||
}
|
||||
|
||||
67
pkg/restore/clusterrolebinding_action.go
Normal file
67
pkg/restore/clusterrolebinding_action.go
Normal 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
|
||||
}
|
||||
118
pkg/restore/clusterrolebinding_action_test.go
Normal file
118
pkg/restore/clusterrolebinding_action_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
67
pkg/restore/rolebinding_action.go
Normal file
67
pkg/restore/rolebinding_action.go
Normal 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
|
||||
}
|
||||
118
pkg/restore/rolebinding_action_test.go
Normal file
118
pkg/restore/rolebinding_action_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user