diff --git a/changelogs/unreleased/4028-zubron b/changelogs/unreleased/4028-zubron new file mode 100644 index 0000000000..79a32656d8 --- /dev/null +++ b/changelogs/unreleased/4028-zubron @@ -0,0 +1 @@ +Add a RestoreItemAction plugin (`velero.io/apiservice`) which skips the restore of any `APIService` which is managed by Kubernetes. These are identified using the `kube-aggregator.kubernetes.io/automanaged` label. \ No newline at end of file diff --git a/go.mod b/go.mod index ccfe26286e..273c588e48 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,9 @@ require ( k8s.io/cli-runtime v0.19.12 k8s.io/client-go v0.19.12 k8s.io/klog v1.0.0 + k8s.io/klog/v2 v2.3.0 // indirect + k8s.io/kube-aggregator v0.19.12 + k8s.io/utils v0.0.0-20201005171033-6301aaf42dc7 // indirect sigs.k8s.io/cluster-api v0.3.11-0.20210106212952-b6c1b5b3db3d sigs.k8s.io/controller-runtime v0.7.1-0.20201215171748-096b2e07c091 ) diff --git a/go.sum b/go.sum index e90361e3ba..204c3485e4 100644 --- a/go.sum +++ b/go.sum @@ -859,14 +859,19 @@ k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.3.0 h1:WmkrnW7fdrm0/DMClc+HIxtftvxVIPAhlVwMQo5yLco= +k8s.io/klog/v2 v2.3.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-aggregator v0.19.12 h1:OwyNUe/7/gxzEnaLd3sC9Yrpx0fZAERzvFslX5Qq5g8= +k8s.io/kube-aggregator v0.19.12/go.mod h1:K76wPd03pSHEmS1FgJOcpryac5C3va4cbCvSu+4EmE0= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kubectl v0.19.2/go.mod h1:4ib3oj5ma6gF95QukTvC7ZBMxp60+UEAhDPjLuBIrV4= k8s.io/metrics v0.19.2/go.mod h1:IlLaAGXN0q7yrtB+SV0q3JIraf6VtlDr+iuTcX21fCU= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20200912215256-4140de9c8800 h1:9ZNvfPvVIEsp/T1ez4GQuzCcCTEQWhovSofhqR73A6g= k8s.io/utils v0.0.0-20200912215256-4140de9c8800/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20201005171033-6301aaf42dc7 h1:XQ0OMFdRDkDIu0b1zqEKSZdWUD7I4bZ4d4nqr8CLKbQ= +k8s.io/utils v0.0.0-20201005171033-6301aaf42dc7/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= diff --git a/pkg/cmd/server/plugin/plugin.go b/pkg/cmd/server/plugin/plugin.go index 58e2572681..1a79b6dd6f 100644 --- a/pkg/cmd/server/plugin/plugin.go +++ b/pkg/cmd/server/plugin/plugin.go @@ -54,6 +54,7 @@ func NewCommand(f client.Factory) *cobra.Command { RegisterRestoreItemAction("velero.io/cluster-role-bindings", newClusterRoleBindingItemAction). RegisterRestoreItemAction("velero.io/crd-preserve-fields", newCRDV1PreserveUnknownFieldsItemAction). RegisterRestoreItemAction("velero.io/change-pvc-node-selector", newChangePVCNodeSelectorItemAction(f)). + RegisterRestoreItemAction("velero.io/apiservice", newAPIServiceRestoreItemAction). Serve() }, } @@ -197,3 +198,7 @@ func newChangePVCNodeSelectorItemAction(f client.Factory) veleroplugin.HandlerIn ), nil } } + +func newAPIServiceRestoreItemAction(logger logrus.FieldLogger) (interface{}, error) { + return restore.NewAPIServiceAction(logger), nil +} diff --git a/pkg/restore/apiservice_action.go b/pkg/restore/apiservice_action.go new file mode 100644 index 0000000000..7f817a59e0 --- /dev/null +++ b/pkg/restore/apiservice_action.go @@ -0,0 +1,51 @@ +/* +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/sirupsen/logrus" + "k8s.io/kube-aggregator/pkg/controllers/autoregister" + + "github.com/vmware-tanzu/velero/pkg/plugin/velero" +) + +type APIServiceAction struct { + logger logrus.FieldLogger +} + +// NewAPIServiceAction returns an APIServiceAction which is a RestoreItemAction plugin +// that will skip the restore of any APIServices which are managed by Kubernetes. This +// is determined by looking for the "kube-aggregator.kubernetes.io/automanaged" label on +// the APIService. +func NewAPIServiceAction(logger logrus.FieldLogger) *APIServiceAction { + return &APIServiceAction{logger: logger} +} + +func (a *APIServiceAction) AppliesTo() (velero.ResourceSelector, error) { + return velero.ResourceSelector{ + IncludedResources: []string{"apiservices"}, + LabelSelector: autoregister.AutoRegisterManagedLabel, + }, nil +} + +func (a *APIServiceAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) { + a.logger.Info("Executing APIServiceAction") + defer a.logger.Info("Done executing APIServiceAction") + + a.logger.Infof("Skipping restore of APIService as it is managed by Kubernetes") + return velero.NewRestoreItemActionExecuteOutput(input.Item).WithoutRestore(), nil +} diff --git a/pkg/restore/apiservice_action_test.go b/pkg/restore/apiservice_action_test.go new file mode 100644 index 0000000000..81f4a6171c --- /dev/null +++ b/pkg/restore/apiservice_action_test.go @@ -0,0 +1,53 @@ +/* +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 ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + velerotest "github.com/vmware-tanzu/velero/pkg/test" +) + +func TestAPIServiceActionExecuteSkipsRestore(t *testing.T) { + obj := apiregistrationv1.APIService{ + ObjectMeta: metav1.ObjectMeta{ + Name: "v1.test.velero.io", + }, + } + + unstructuredAPIService, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&obj) + require.NoError(t, err) + + action := NewAPIServiceAction(velerotest.NewLogger()) + res, err := action.Execute(&velero.RestoreItemActionExecuteInput{ + Item: &unstructured.Unstructured{Object: unstructuredAPIService}, + ItemFromBackup: &unstructured.Unstructured{Object: unstructuredAPIService}, + }) + require.NoError(t, err) + + var apiService apiregistrationv1.APIService + require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(res.UpdatedItem.UnstructuredContent(), &apiService)) + require.Equal(t, obj, apiService) + require.Equal(t, true, res.SkipRestore) +} diff --git a/pkg/restore/restore_test.go b/pkg/restore/restore_test.go index 9450263964..2743978625 100644 --- a/pkg/restore/restore_test.go +++ b/pkg/restore/restore_test.go @@ -1,5 +1,5 @@ /* -Copyright 2019 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. @@ -1139,6 +1139,22 @@ func TestRestoreActionsRunForCorrectItems(t *testing.T) { new(recordResourcesAction).ForNamespace("ns-1").ForResource("pods"): {"ns-1/pod-1"}, }, }, + { + name: "single action with a resource and label selector runs only for resources matching that label", + restore: defaultRestore().Result(), + backup: defaultBackup().Result(), + tarball: test.NewTarWriter(t). + AddItems("pods", + builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("restore-resource", "true")).Result(), + builder.ForPod("ns-1", "pod-2").ObjectMeta(builder.WithLabels("do-not-restore-resource", "true")).Result(), + builder.ForPod("ns-2", "pod-1").Result(), + builder.ForPod("ns-2", "pod-2").ObjectMeta(builder.WithLabels("restore-resource")).Result(), + ).Done(), + apiResources: []*test.APIResource{test.Pods()}, + actions: map[*recordResourcesAction][]string{ + new(recordResourcesAction).ForResource("pods").ForLabelSelector("restore-resource"): {"ns-1/pod-1", "ns-2/pod-2"}, + }, + }, { name: "multiple actions, each with a different resource selector using short name, run for matching resources", restore: defaultRestore().Result(),