diff --git a/internal/resourcemodifiers/resource_modifiers_test.go b/internal/resourcemodifiers/resource_modifiers_test.go index f857f5e58..7d739f4ee 100644 --- a/internal/resourcemodifiers/resource_modifiers_test.go +++ b/internal/resourcemodifiers/resource_modifiers_test.go @@ -36,7 +36,7 @@ func TestGetResourceModifiersFromConfig(t *testing.T) { ResourceModifierRules: []ResourceModifierRule{ { Conditions: Conditions{ - GroupKind: "persistentvolumeclaims.storage.k8s.io", + GroupKind: "persistentvolumeclaims", ResourceNameRegex: ".*", Namespaces: []string{"bar", "foo"}, }, @@ -64,7 +64,8 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) { pvcStandardSc := &unstructured.Unstructured{ Object: map[string]interface{}{ - "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", "metadata": map[string]interface{}{ "name": "test-pvc", "namespace": "foo", @@ -77,7 +78,8 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) { pvcPremiumSc := &unstructured.Unstructured{ Object: map[string]interface{}{ - "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", "metadata": map[string]interface{}{ "name": "test-pvc", "namespace": "foo", @@ -88,6 +90,45 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) { }, } + deployNginxOneReplica := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-deployment", + "namespace": "foo", + }, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + "image": "nginx:latest", + }, + }, + "replicas": "1", + }, + }, + } + deployNginxTwoReplica := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-deployment", + "namespace": "foo", + }, + "spec": map[string]interface{}{ + "containers": []interface{}{ + map[string]interface{}{ + "name": "nginx", + "image": "nginx:latest", + }, + }, + "replicas": "2", + }, + }, + } + type fields struct { Version string ResourceModifierRules []ResourceModifierRule @@ -100,11 +141,11 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) { name string fields fields args args - want []error + wantErr bool wantObj *unstructured.Unstructured }{ { - name: "test1", + name: "pvc with standard storage class should be patched to premium", fields: fields{ Version: "v1", ResourceModifierRules: []ResourceModifierRule{ @@ -133,10 +174,140 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) { obj: pvcStandardSc.DeepCopy(), groupResource: "persistentvolumeclaims", }, - want: nil, + wantErr: false, wantObj: pvcPremiumSc.DeepCopy(), }, + { + name: "nginx deployment: 1 -> 2 replicas", + fields: fields{ + Version: "v1", + ResourceModifierRules: []ResourceModifierRule{ + { + Conditions: Conditions{ + GroupKind: "deployments.apps", + ResourceNameRegex: "test-.*", + Namespaces: []string{"foo"}, + }, + Patches: []JSONPatch{ + { + Operation: "test", + Path: "/spec/replicas", + Value: "1", + }, + { + Operation: "replace", + Path: "/spec/replicas", + Value: "2", + }, + }, + }, + }, + }, + args: args{ + obj: deployNginxOneReplica.DeepCopy(), + groupResource: "deployments.apps", + }, + wantErr: false, + wantObj: deployNginxTwoReplica.DeepCopy(), + }, + { + name: "nginx deployment: Empty Resource Regex", + fields: fields{ + Version: "v1", + ResourceModifierRules: []ResourceModifierRule{ + { + Conditions: Conditions{ + GroupKind: "deployments.apps", + Namespaces: []string{"foo"}, + }, + Patches: []JSONPatch{ + { + Operation: "test", + Path: "/spec/replicas", + Value: "1", + }, + { + Operation: "replace", + Path: "/spec/replicas", + Value: "2", + }, + }, + }, + }, + }, + args: args{ + obj: deployNginxOneReplica.DeepCopy(), + groupResource: "deployments.apps", + }, + wantErr: false, + wantObj: deployNginxTwoReplica.DeepCopy(), + }, + { + name: "nginx deployment: Empty Resource Regex and namespaces list", + fields: fields{ + Version: "v1", + ResourceModifierRules: []ResourceModifierRule{ + { + Conditions: Conditions{ + GroupKind: "deployments.apps", + }, + Patches: []JSONPatch{ + { + Operation: "test", + Path: "/spec/replicas", + Value: "1", + }, + { + Operation: "replace", + Path: "/spec/replicas", + Value: "2", + }, + }, + }, + }, + }, + args: args{ + obj: deployNginxOneReplica.DeepCopy(), + groupResource: "deployments.apps", + }, + wantErr: false, + wantObj: deployNginxTwoReplica.DeepCopy(), + }, + { + name: "nginx deployment: namespace doesn't match", + fields: fields{ + Version: "v1", + ResourceModifierRules: []ResourceModifierRule{ + { + Conditions: Conditions{ + GroupKind: "deployments.apps", + ResourceNameRegex: ".*", + Namespaces: []string{"bar"}, + }, + Patches: []JSONPatch{ + { + Operation: "test", + Path: "/spec/replicas", + Value: "1", + }, + { + Operation: "replace", + Path: "/spec/replicas", + Value: "2", + }, + }, + }, + }, + }, + args: args{ + obj: deployNginxOneReplica.DeepCopy(), + groupResource: "deployments.apps", + }, + wantErr: false, + wantObj: deployNginxOneReplica.DeepCopy(), + }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &ResourceModifiers{ @@ -145,7 +316,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) { } got := p.ApplyResourceModifierRules(tt.args.obj, tt.args.groupResource, logrus.New()) - assert.Equal(t, len(tt.want), len(got)) + assert.Equal(t, tt.wantErr, len(got) > 0) assert.Equal(t, *tt.wantObj, *tt.args.obj) }) } diff --git a/pkg/apis/velero/v1/restore_types.go b/pkg/apis/velero/v1/restore_types.go index 985d7d23d..c1dafa740 100644 --- a/pkg/apis/velero/v1/restore_types.go +++ b/pkg/apis/velero/v1/restore_types.go @@ -121,6 +121,7 @@ type RestoreSpec struct { // ResourceModifier specifies the reference to JSON resource patches that should be applied to resources before restoration. // +optional + // +nullable ResourceModifier *v1.TypedLocalObjectReference `json:"resourceModifier,omitempty"` } diff --git a/pkg/cmd/cli/restore/create.go b/pkg/cmd/cli/restore/create.go index 16cfe6825..95555aa34 100644 --- a/pkg/cmd/cli/restore/create.go +++ b/pkg/cmd/cli/restore/create.go @@ -268,6 +268,15 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { } } + var resModifiers *corev1.TypedLocalObjectReference = nil + + if o.ResourceModifierConfigMap != "" { + resModifiers = &corev1.TypedLocalObjectReference{ + Kind: resourcemodifiers.ConfigmapRefType, + Name: o.ResourceModifierConfigMap, + } + } + restore := &api.Restore{ ObjectMeta: metav1.ObjectMeta{ Namespace: f.Namespace(), @@ -287,10 +296,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { RestorePVs: o.RestoreVolumes.Value, PreserveNodePorts: o.PreserveNodePorts.Value, IncludeClusterResources: o.IncludeClusterResources.Value, - ResourceModifier: &corev1.TypedLocalObjectReference{ - Kind: resourcemodifiers.ConfigmapRefType, - Name: o.ResourceModifierConfigMap, - }, + ResourceModifier: resModifiers, ItemOperationTimeout: metav1.Duration{ Duration: o.ItemOperationTimeout, }, diff --git a/test/e2e/util/velero/velero_utils.go b/test/e2e/util/velero/velero_utils.go index 23e5adc71..7da65f945 100644 --- a/test/e2e/util/velero/velero_utils.go +++ b/test/e2e/util/velero/velero_utils.go @@ -179,7 +179,6 @@ func checkRestorePhase(ctx context.Context, veleroCLI string, veleroNamespace st return err } if restore.Status.Phase != expectedPhase { - fmt.Println(restore.Status.FailureReason) return errors.Errorf("Unexpected restore phase got %s, expecting %s", restore.Status.Phase, expectedPhase) } return nil