mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-15 19:56:06 +00:00
Merge pull request #4397 from reasonerjt/fix-3516
Add restoreactionitem plugin to handle admission webhook configurations
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Add restoreactionitem plugin to handle admission webhook configurations
|
||||
@@ -55,6 +55,7 @@ func NewCommand(f client.Factory) *cobra.Command {
|
||||
RegisterRestoreItemAction("velero.io/crd-preserve-fields", newCRDV1PreserveUnknownFieldsItemAction).
|
||||
RegisterRestoreItemAction("velero.io/change-pvc-node-selector", newChangePVCNodeSelectorItemAction(f)).
|
||||
RegisterRestoreItemAction("velero.io/apiservice", newAPIServiceRestoreItemAction).
|
||||
RegisterRestoreItemAction("velero.io/admission-webhook-configuration", newAdmissionWebhookConfigurationAction).
|
||||
Serve()
|
||||
},
|
||||
}
|
||||
@@ -202,3 +203,7 @@ func newChangePVCNodeSelectorItemAction(f client.Factory) veleroplugin.HandlerIn
|
||||
func newAPIServiceRestoreItemAction(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return restore.NewAPIServiceAction(logger), nil
|
||||
}
|
||||
|
||||
func newAdmissionWebhookConfigurationAction(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return restore.NewAdmissionWebhookConfigurationAction(logger), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright 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"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
)
|
||||
|
||||
// AdmissionWebhookConfigurationAction is a RestoreItemAction plugin applicable to mutatingwebhookconfiguration and
|
||||
// validatingwebhookconfiguration to reset the invalid value for "sideEffects" of the webhooks.
|
||||
// More background please refer to https://github.com/vmware-tanzu/velero/issues/3516
|
||||
type AdmissionWebhookConfigurationAction struct {
|
||||
logger logrus.FieldLogger
|
||||
}
|
||||
|
||||
// NewAdmissionWebhookConfigurationAction creates a new instance of AdmissionWebhookConfigurationAction
|
||||
func NewAdmissionWebhookConfigurationAction(logger logrus.FieldLogger) *AdmissionWebhookConfigurationAction {
|
||||
return &AdmissionWebhookConfigurationAction{logger: logger}
|
||||
}
|
||||
|
||||
// AppliesTo implements the RestoreItemAction plugin interface method.
|
||||
func (a *AdmissionWebhookConfigurationAction) AppliesTo() (velero.ResourceSelector, error) {
|
||||
return velero.ResourceSelector{
|
||||
IncludedResources: []string{"mutatingwebhookconfigurations", "validatingwebhookconfigurations"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Execute will reset the value of "sideEffects" attribute of each item in the "webhooks" list to "None" if they are invalid values for
|
||||
// v1, such as "Unknown" or "Some"
|
||||
func (a *AdmissionWebhookConfigurationAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
|
||||
a.logger.Info("Executing ChangeStorageClassAction")
|
||||
defer a.logger.Info("Done executing ChangeStorageClassAction")
|
||||
|
||||
item := input.Item
|
||||
apiVersion, _, err := unstructured.NestedString(item.UnstructuredContent(), "apiVersion")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get the apiVersion from input item")
|
||||
}
|
||||
name, _, _ := unstructured.NestedString(item.UnstructuredContent(), "metadata", "name")
|
||||
logger := a.logger.WithField("resource_name", name)
|
||||
if apiVersion != "admissionregistration.k8s.io/v1" {
|
||||
logger.Infof("unable to handle api version: %s, skip", apiVersion)
|
||||
return velero.NewRestoreItemActionExecuteOutput(input.Item), nil
|
||||
}
|
||||
webhooks, ok, err := unstructured.NestedSlice(item.UnstructuredContent(), "webhooks")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get webhooks slice from input item")
|
||||
}
|
||||
if !ok {
|
||||
logger.Info("webhooks is not set, skip")
|
||||
return velero.NewRestoreItemActionExecuteOutput(input.Item), nil
|
||||
}
|
||||
newWebhooks := make([]interface{}, 0)
|
||||
for i, entry := range webhooks {
|
||||
logger2 := logger.WithField("index", i)
|
||||
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&entry)
|
||||
if err != nil {
|
||||
logger2.Errorf("failed to convert the webhook entry, error: %v, it will be dropped", err)
|
||||
continue
|
||||
}
|
||||
s, _, _ := unstructured.NestedString(obj, "sideEffects")
|
||||
if s != "None" && s != "NoneOnDryRun" {
|
||||
logger2.Infof("reset the invalid sideEffects value '%s' to 'None'", s)
|
||||
obj["sideEffects"] = "None"
|
||||
}
|
||||
newWebhooks = append(newWebhooks, obj)
|
||||
}
|
||||
item.UnstructuredContent()["webhooks"] = newWebhooks
|
||||
return velero.NewRestoreItemActionExecuteOutput(item), nil
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package restore
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
func TestNewAdmissionWebhookConfigurationActionExecute(t *testing.T) {
|
||||
action := NewAdmissionWebhookConfigurationAction(velerotest.NewLogger())
|
||||
cases := []struct {
|
||||
name string
|
||||
itemJSON string
|
||||
wantErr bool
|
||||
NoneSideEffectsIndex []int // the indexes with sideEffects that arereset to None
|
||||
NotNoneSideEffectsIndex []int // the indexes with sideEffects that are not reset to None
|
||||
}{
|
||||
{
|
||||
name: "v1 mutatingwebhookconfiguration with sideEffects as Unknown",
|
||||
itemJSON: `{
|
||||
"apiVersion": "admissionregistration.k8s.io/v1",
|
||||
"kind": "MutatingWebhookConfiguration",
|
||||
"metadata": {
|
||||
"name": "my-test-mutating"
|
||||
},
|
||||
"webhooks": [
|
||||
{
|
||||
"clientConfig": {
|
||||
"url": "https://mytest.org"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1"
|
||||
],
|
||||
"operations": [
|
||||
"CREATE"
|
||||
],
|
||||
"resources": [
|
||||
"pods"
|
||||
],
|
||||
"scope": "Namespaced"
|
||||
}
|
||||
],
|
||||
"sideEffects": "Unknown"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantErr: false,
|
||||
NoneSideEffectsIndex: []int{0},
|
||||
},
|
||||
{
|
||||
name: "v1 validatingwebhookconfiguration with sideEffects as Some",
|
||||
itemJSON: `{
|
||||
"apiVersion": "admissionregistration.k8s.io/v1",
|
||||
"kind": "ValidatingWebhookConfiguration",
|
||||
"metadata": {
|
||||
"name": "my-test-validating"
|
||||
},
|
||||
"webhooks": [
|
||||
{
|
||||
"clientConfig": {
|
||||
"url": "https://mytest.org"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1"
|
||||
],
|
||||
"operations": [
|
||||
"CREATE"
|
||||
],
|
||||
"resources": [
|
||||
"pods"
|
||||
],
|
||||
"scope": "Namespaced"
|
||||
}
|
||||
],
|
||||
"sideEffects": "Some"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantErr: false,
|
||||
NoneSideEffectsIndex: []int{0},
|
||||
},
|
||||
{
|
||||
name: "v1beta1 validatingwebhookconfiguration with sideEffects as Some, nothing should change",
|
||||
itemJSON: `{
|
||||
"apiVersion": "admissionregistration.k8s.io/v1beta1",
|
||||
"kind": "ValidatingWebhookConfiguration",
|
||||
"metadata": {
|
||||
"name": "my-test-validating"
|
||||
},
|
||||
"webhooks": [
|
||||
{
|
||||
"clientConfig": {
|
||||
"url": "https://mytest.org"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"apiVersions": [
|
||||
"v1"
|
||||
],
|
||||
"operations": [
|
||||
"CREATE"
|
||||
],
|
||||
"resources": [
|
||||
"pods"
|
||||
],
|
||||
"scope": "Namespaced"
|
||||
}
|
||||
],
|
||||
"sideEffects": "Some"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantErr: false,
|
||||
NotNoneSideEffectsIndex: []int{0},
|
||||
},
|
||||
{
|
||||
name: "v1 validatingwebhookconfiguration with multiple invalid sideEffects",
|
||||
itemJSON: `{
|
||||
"apiVersion": "admissionregistration.k8s.io/v1",
|
||||
"kind": "ValidatingWebhookConfiguration",
|
||||
"metadata": {
|
||||
"name": "my-test-validating"
|
||||
},
|
||||
"webhooks": [
|
||||
{
|
||||
"clientConfig": {
|
||||
"url": "https://mytest.org"
|
||||
},
|
||||
"sideEffects": "Some"
|
||||
},
|
||||
{
|
||||
"clientConfig": {
|
||||
"url": "https://mytest2.org"
|
||||
},
|
||||
"sideEffects": "Some"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantErr: false,
|
||||
NoneSideEffectsIndex: []int{0, 1},
|
||||
},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := map[string]interface{}{}
|
||||
json.Unmarshal([]byte(tt.itemJSON), &o)
|
||||
input := &velero.RestoreItemActionExecuteInput{
|
||||
Item: &unstructured.Unstructured{
|
||||
Object: o,
|
||||
},
|
||||
}
|
||||
output, err := action.Execute(input)
|
||||
if tt.wantErr {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
if tt.NoneSideEffectsIndex != nil {
|
||||
wb, _, err := unstructured.NestedSlice(output.UpdatedItem.UnstructuredContent(), "webhooks")
|
||||
assert.Nil(t, err)
|
||||
for _, i := range tt.NoneSideEffectsIndex {
|
||||
it, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&wb[i])
|
||||
assert.Nil(t, err)
|
||||
s := it["sideEffects"].(string)
|
||||
assert.Equal(t, "None", s)
|
||||
}
|
||||
}
|
||||
if tt.NotNoneSideEffectsIndex != nil {
|
||||
wb, _, err := unstructured.NestedSlice(output.UpdatedItem.UnstructuredContent(), "webhooks")
|
||||
assert.Nil(t, err)
|
||||
for _, i := range tt.NotNoneSideEffectsIndex {
|
||||
it, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&wb[i])
|
||||
assert.Nil(t, err)
|
||||
s := it["sideEffects"].(string)
|
||||
assert.NotEqual(t, "None", s)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2017 the Velero contributors.
|
||||
Copyright 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2018 the Velero contributors.
|
||||
Copyright 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.
|
||||
@@ -13,6 +13,7 @@ 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 (
|
||||
|
||||
Reference in New Issue
Block a user