karmada/pkg/util/validation/validation_test.go

815 lines
24 KiB
Go

/*
Copyright 2021 The Karmada Authors.
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 validation
import (
"archive/tar"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
"github.com/karmada-io/karmada/pkg/util"
)
func TestValidateOverrideSpec(t *testing.T) {
var tests = []struct {
name string
overrideSpec policyv1alpha1.OverrideSpec
expectError bool
}{
{
name: "overrideRules is set, overriders and targetCluster aren't set",
overrideSpec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"cluster-name"},
},
},
},
},
expectError: false,
},
{
name: "overriders and targetCluster are set, overrideRules isn't set",
overrideSpec: policyv1alpha1.OverrideSpec{
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"cluster-name"},
},
Overriders: policyv1alpha1.Overriders{
Plaintext: []policyv1alpha1.PlaintextOverrider{
{
Path: "spec/image",
},
},
},
},
expectError: false,
},
{
name: "overrideRules and targetCluster can't co-exist",
overrideSpec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"cluster-name"},
},
},
},
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"cluster-name"},
},
},
expectError: true,
},
{
name: "overrideRules and overriders can't co-exist",
overrideSpec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"cluster-name"},
},
},
},
Overriders: policyv1alpha1.Overriders{
Plaintext: []policyv1alpha1.PlaintextOverrider{
{
Path: "spec/image",
},
},
},
},
expectError: true,
},
{
name: "overrideRules, targetCluster and overriders can't co-exist",
overrideSpec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"cluster-name"},
},
},
},
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"cluster-name"},
},
Overriders: policyv1alpha1.Overriders{
Plaintext: []policyv1alpha1.PlaintextOverrider{
{
Path: "spec/image",
},
},
},
},
expectError: true,
},
{
name: "invalid annotation should not be allowed",
overrideSpec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"cluster-name"},
},
Overriders: policyv1alpha1.Overriders{
AnnotationsOverrider: []policyv1alpha1.LabelAnnotationOverrider{
{
Operator: policyv1alpha1.OverriderOpAdd,
Value: map[string]string{"testannotation~projectId": "c-m-lfx9lk92p-v86cf"},
},
},
},
},
},
},
expectError: true,
},
{
name: "invalid label should not be allowed",
overrideSpec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"cluster-name"},
},
Overriders: policyv1alpha1.Overriders{
LabelsOverrider: []policyv1alpha1.LabelAnnotationOverrider{
{
Operator: policyv1alpha1.OverriderOpAdd,
Value: map[string]string{"testannotation~projectId": "c-m-lfx9lk92p-v86cf"},
},
},
},
},
},
},
expectError: true,
},
{
name: "overrideSpec.targetCluster.fieldSelector has unsupported key",
overrideSpec: policyv1alpha1.OverrideSpec{
TargetCluster: &policyv1alpha1.ClusterAffinity{
FieldSelector: &policyv1alpha1.FieldSelector{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "foo",
Operator: corev1.NodeSelectorOpIn,
Values: []string{"fooCloud"},
}}}},
},
expectError: true,
},
{
name: "overrideSpec.targetCluster.fieldSelector has unsupported operator",
overrideSpec: policyv1alpha1.OverrideSpec{
TargetCluster: &policyv1alpha1.ClusterAffinity{
FieldSelector: &policyv1alpha1.FieldSelector{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: util.ProviderField,
Operator: corev1.NodeSelectorOpGt,
Values: []string{"fooCloud"},
}}}},
},
expectError: true,
},
{
name: "overrideRules.[index].targetCluster.fieldSelector has unsupported key",
overrideSpec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{
FieldSelector: &policyv1alpha1.FieldSelector{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "foo",
Operator: corev1.NodeSelectorOpIn,
Values: []string{"fooCloud"},
}}},
},
},
},
},
expectError: true,
},
{
name: "overrideRules.[index].targetCluster.fieldSelector has unsupported operator",
overrideSpec: policyv1alpha1.OverrideSpec{
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{
FieldSelector: &policyv1alpha1.FieldSelector{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: util.ProviderField,
Operator: corev1.NodeSelectorOpGt,
Values: []string{"fooCloud"},
}}},
},
},
},
},
expectError: true,
},
}
for _, test := range tests {
tc := test
err := ValidateOverrideSpec(&tc.overrideSpec)
if err != nil && tc.expectError != true {
t.Fatalf("expect no error but got: %v", err)
}
if err == nil && tc.expectError == true {
t.Fatalf("expect an error but got none")
}
}
}
func TestEmptyOverrides(t *testing.T) {
tests := []struct {
name string
overriders policyv1alpha1.Overriders
want bool
}{
{
name: "empty overrides",
overriders: policyv1alpha1.Overriders{},
want: true,
},
{
name: "non-empty overrides",
overriders: policyv1alpha1.Overriders{
Plaintext: []policyv1alpha1.PlaintextOverrider{
{
Path: "spec/image",
},
},
},
want: false,
},
{
name: "non-empty overrides",
overriders: policyv1alpha1.Overriders{
Plaintext: []policyv1alpha1.PlaintextOverrider{
{
Path: "spec/image",
},
},
ImageOverrider: []policyv1alpha1.ImageOverrider{
{
Component: "Registry",
Operator: policyv1alpha1.OverriderOpRemove,
Value: "fictional.registry.us",
},
},
CommandOverrider: []policyv1alpha1.CommandArgsOverrider{
{
ContainerName: "nginx",
Operator: policyv1alpha1.OverriderOpAdd,
Value: []string{"echo 'hello karmada'"},
},
},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := emptyOverrides(tt.overriders); got != tt.want {
t.Errorf("EmptyOverrides() = %v, want %v", got, tt.want)
}
})
}
}
func TestValidatePropagationSpec(t *testing.T) {
tests := []struct {
name string
spec policyv1alpha1.PropagationSpec
expectedErr string
}{
{
name: "valid spec",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
FieldSelector: &policyv1alpha1.FieldSelector{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: util.ProviderField,
Operator: corev1.NodeSelectorOpIn,
Values: []string{"fooCloud"},
},
{
Key: util.RegionField,
Operator: corev1.NodeSelectorOpNotIn,
Values: []string{"fooCloud"},
},
{
Key: util.ZoneField,
Operator: corev1.NodeSelectorOpNotIn,
Values: []string{"fooCloud"},
},
}}},
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
{
MaxGroups: 2,
MinGroups: 1,
SpreadByField: policyv1alpha1.SpreadByFieldRegion,
},
{
SpreadByField: policyv1alpha1.SpreadByFieldCluster,
}}}},
expectedErr: "",
},
{
name: "clusterAffinity.fieldSelector has unsupported key",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
FieldSelector: &policyv1alpha1.FieldSelector{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "foo",
Operator: corev1.NodeSelectorOpIn,
Values: []string{"fooCloud"},
}}}}}},
expectedErr: "unsupported key \"foo\", must be provider, region, or zone",
},
{
name: "clusterAffinity.fieldSelector has unsupported operator",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
FieldSelector: &policyv1alpha1.FieldSelector{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: util.ProviderField,
Operator: corev1.NodeSelectorOpExists,
Values: []string{"fooCloud"},
}}}}}},
expectedErr: "unsupported operator \"Exists\", must be In or NotIn",
},
{
name: "clusterAffinities cannot co-exist with clusterAffinity",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"m1"},
},
ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{
{
AffinityName: "group1",
ClusterAffinity: policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"m1"},
}}}}},
expectedErr: "clusterAffinities cannot co-exist with clusterAffinity",
},
{
name: "clusterAffinities different affinities have the same affinityName",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{
{
AffinityName: "group1",
ClusterAffinity: policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"m1"},
}},
{
AffinityName: "group1",
ClusterAffinity: policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"m2"},
}}}}},
expectedErr: "each affinity term in a policy must have a unique name",
},
{
name: "clusterAffinities.[index].clusterAffinity.fieldSelector has unsupported key",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{
{
AffinityName: "group1",
ClusterAffinity: policyv1alpha1.ClusterAffinity{
FieldSelector: &policyv1alpha1.FieldSelector{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "foo",
Operator: corev1.NodeSelectorOpIn,
Values: []string{"fooCloud"},
}}}}}}}},
expectedErr: "unsupported key \"foo\", must be provider, region, or zone",
},
{
name: "clusterAffinities.[index].clusterAffinity.fieldSelector has unsupported operator",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
ClusterAffinities: []policyv1alpha1.ClusterAffinityTerm{
{
AffinityName: "group1",
ClusterAffinity: policyv1alpha1.ClusterAffinity{
FieldSelector: &policyv1alpha1.FieldSelector{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: util.ProviderField,
Operator: corev1.NodeSelectorOpExists,
Values: []string{"fooCloud"},
}}}}}}}},
expectedErr: "unsupported operator \"Exists\", must be In or NotIn",
},
{
name: "spreadConstraint spreadByLabel co-exist with spreadByField",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
{
SpreadByField: policyv1alpha1.SpreadByFieldCluster,
SpreadByLabel: "foo",
},
},
}},
expectedErr: "spreadByLabel should not co-exist with spreadByField",
},
{
name: "spreadConstraint maxGroups lower than minGroups",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
{
MaxGroups: 1,
MinGroups: 2,
},
},
}},
expectedErr: "maxGroups lower than minGroups is not allowed",
},
{
name: "spreadConstraint maxGroups lower than 0",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
{
MaxGroups: -1,
MinGroups: 1,
},
},
}},
expectedErr: "maxGroups lower than 0 is not allowed",
},
{
name: "spreadConstraint minGroups lower than 0",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
{
MaxGroups: 2,
MinGroups: -2,
},
},
}},
expectedErr: "minGroups lower than 0 is not allowed",
},
{
name: "spreadConstraint has two cluster spread constraints",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
{
SpreadByField: policyv1alpha1.SpreadByFieldCluster,
MaxGroups: 2,
MinGroups: -2,
},
{
SpreadByField: policyv1alpha1.SpreadByFieldCluster,
MaxGroups: 5,
MinGroups: 3,
},
{
SpreadByLabel: "grouped-by-net",
MaxGroups: 5,
MinGroups: 3,
},
},
}},
expectedErr: "multiple cluster spread constraints are not allowed",
},
{
name: "spreadConstraint has multiple region spread constraints",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
{
SpreadByField: policyv1alpha1.SpreadByFieldRegion,
MaxGroups: 1,
MinGroups: 3,
},
{
SpreadByField: policyv1alpha1.SpreadByFieldRegion,
MaxGroups: 4,
MinGroups: 2,
},
{
SpreadByField: policyv1alpha1.SpreadByFieldRegion,
MaxGroups: 6,
MinGroups: 5,
},
{
SpreadByField: policyv1alpha1.SpreadByFieldCluster,
MaxGroups: 10,
MinGroups: 5,
},
},
}},
expectedErr: "multiple region spread constraints are not allowed",
},
{
name: "spreadConstraint spreadByFieldCluster must be included if using spreadByField",
spec: policyv1alpha1.PropagationSpec{
Placement: policyv1alpha1.Placement{
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
{
SpreadByField: policyv1alpha1.SpreadByFieldRegion,
},
},
}},
expectedErr: "the cluster spread constraint must be enabled in one of the constraints in case of SpreadByField is enabled",
},
{
name: "resourceSelector name is empty when preemption is enabled",
spec: policyv1alpha1.PropagationSpec{
ResourceSelectors: []policyv1alpha1.ResourceSelector{
{
APIVersion: "v1",
Kind: "Pod",
Namespace: "default",
},
},
Preemption: policyv1alpha1.PreemptAlways,
},
expectedErr: "name cannot be empty if preemption is Always, the empty name may cause unexpected resources preemption",
},
{
name: "suspension dispatching cannot co-exist with dispatchingOnClusters",
spec: policyv1alpha1.PropagationSpec{
Suspension: &policyv1alpha1.Suspension{
Dispatching: ptr.To(true),
DispatchingOnClusters: &policyv1alpha1.SuspendClusters{
ClusterNames: []string{"cluster-name"},
},
},
},
expectedErr: "suspension dispatching cannot co-exist with dispatchingOnClusters.clusterNames",
},
{
name: "suspension dispatching with nil dispatchingOnClusters is valid",
spec: policyv1alpha1.PropagationSpec{
Suspension: &policyv1alpha1.Suspension{
Dispatching: ptr.To(true),
},
},
expectedErr: "",
},
{
name: "suspension dispatching with empty dispatching clusters is valid",
spec: policyv1alpha1.PropagationSpec{
Suspension: &policyv1alpha1.Suspension{
Dispatching: ptr.To(true),
DispatchingOnClusters: &policyv1alpha1.SuspendClusters{
ClusterNames: []string{},
},
},
},
expectedErr: "",
},
{
name: "dispatchingOnClusters.clusterNames without dispatching is valid",
spec: policyv1alpha1.PropagationSpec{
Suspension: &policyv1alpha1.Suspension{
DispatchingOnClusters: &policyv1alpha1.SuspendClusters{
ClusterNames: []string{"cluster-name"},
},
},
},
expectedErr: "",
},
{
name: "dispatchingOnClusters.clusterNames with dispatching false is valid",
spec: policyv1alpha1.PropagationSpec{
Suspension: &policyv1alpha1.Suspension{
Dispatching: ptr.To(false),
DispatchingOnClusters: &policyv1alpha1.SuspendClusters{
ClusterNames: []string{"cluster-name"},
},
},
},
expectedErr: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
errs := ValidatePropagationSpec(tt.spec)
err := errs.ToAggregate()
if err != nil {
errStr := err.Error()
if tt.expectedErr == "" {
t.Errorf("expected no error:\n but got:\n %s", errStr)
} else if !strings.Contains(errStr, tt.expectedErr) {
t.Errorf("expected to contain:\n %s\ngot:\n %s", tt.expectedErr, errStr)
}
} else {
if tt.expectedErr != "" {
t.Errorf("unexpected no error, expected to contain:\n %s", tt.expectedErr)
}
}
})
}
}
func TestValidateApplicationFailover(t *testing.T) {
tests := []struct {
name string
applicationFailoverBehavior *policyv1alpha1.ApplicationFailoverBehavior
expectedErr string
}{
{
name: "application failover is nil",
applicationFailoverBehavior: nil,
expectedErr: "",
},
{
name: "the tolerationSeconds is less than zero",
applicationFailoverBehavior: &policyv1alpha1.ApplicationFailoverBehavior{
DecisionConditions: policyv1alpha1.DecisionConditions{
TolerationSeconds: ptr.To[int32](-100),
},
},
expectedErr: "spec.failover.application.decisionConditions.tolerationSeconds: Invalid value: -100: must be greater than or equal to 0",
},
{
name: "the gracePeriodSeconds is declared when purgeMode is not graciously",
applicationFailoverBehavior: &policyv1alpha1.ApplicationFailoverBehavior{
DecisionConditions: policyv1alpha1.DecisionConditions{
TolerationSeconds: ptr.To[int32](100),
},
PurgeMode: policyv1alpha1.Immediately,
GracePeriodSeconds: ptr.To[int32](100),
},
expectedErr: "spec.failover.application.gracePeriodSeconds: Invalid value: 100: only takes effect when purgeMode is graciously",
},
{
name: "the gracePeriodSeconds is less than 0 when purgeMode is graciously",
applicationFailoverBehavior: &policyv1alpha1.ApplicationFailoverBehavior{
DecisionConditions: policyv1alpha1.DecisionConditions{
TolerationSeconds: ptr.To[int32](100),
},
PurgeMode: policyv1alpha1.Graciously,
GracePeriodSeconds: ptr.To[int32](-100),
},
expectedErr: "spec.failover.application.gracePeriodSeconds: Invalid value: -100: must be greater than 0",
},
{
name: "the gracePeriodSeconds is empty when purgeMode is graciously",
applicationFailoverBehavior: &policyv1alpha1.ApplicationFailoverBehavior{
DecisionConditions: policyv1alpha1.DecisionConditions{
TolerationSeconds: ptr.To[int32](100),
},
PurgeMode: policyv1alpha1.Graciously,
},
expectedErr: "spec.failover.application.gracePeriodSeconds: Invalid value: \"null\": should not be empty when purgeMode is graciously",
},
{
name: "application behavior is correctly declared",
applicationFailoverBehavior: &policyv1alpha1.ApplicationFailoverBehavior{
DecisionConditions: policyv1alpha1.DecisionConditions{
TolerationSeconds: ptr.To[int32](100),
},
},
expectedErr: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
errs := ValidateApplicationFailover(tt.applicationFailoverBehavior, field.NewPath("spec").Child("failover").Child("application"))
err := errs.ToAggregate()
if err != nil && err.Error() != tt.expectedErr {
t.Errorf("expected error:\n %s, but got:\n %s", tt.expectedErr, err.Error())
} else if err == nil && tt.expectedErr != "" {
t.Errorf("expected error:\n %s, but got no error\n", tt.expectedErr)
}
})
}
}
func TestValidateCrdsTarBall(t *testing.T) {
testItems := []struct {
name string
header *tar.Header
expectedErr error
}{
{
name: "unclean file dir 'crds/../'",
header: &tar.Header{
Name: "crds/../",
Typeflag: tar.TypeDir,
},
expectedErr: fmt.Errorf("the given file contains unclean file dir: %s", "crds/../"),
},
{
name: "unclean file dir 'crds/..'",
header: &tar.Header{
Name: "crds/..",
Typeflag: tar.TypeDir,
},
expectedErr: fmt.Errorf("the given file contains unclean file dir: %s", "crds/.."),
},
{
name: "unexpected file dir '../crds'",
header: &tar.Header{
Name: "../crds",
Typeflag: tar.TypeDir,
},
expectedErr: fmt.Errorf("the given file contains unexpected file dir: %s", "../crds"),
},
{
name: "unexpected file dir '..'",
header: &tar.Header{
Name: "..",
Typeflag: tar.TypeDir,
},
expectedErr: fmt.Errorf("the given file contains unexpected file dir: %s", ".."),
},
{
name: "expected file dir 'crds/'",
header: &tar.Header{
Name: "crds/",
Typeflag: tar.TypeDir,
},
expectedErr: nil,
},
{
name: "expected file dir 'crds'",
header: &tar.Header{
Name: "crds",
Typeflag: tar.TypeDir,
},
expectedErr: nil,
},
{
name: "unclean file path 'crds/../a.yaml'",
header: &tar.Header{
Name: "crds/../a.yaml",
Typeflag: tar.TypeReg,
},
expectedErr: fmt.Errorf("the given file contains unclean file path: %s", "crds/../a.yaml"),
},
{
name: "unexpected file path '../crds/a.yaml'",
header: &tar.Header{
Name: "../crds/a.yaml",
Typeflag: tar.TypeReg,
},
expectedErr: fmt.Errorf("the given file contains unexpected file path: %s", "../crds/a.yaml"),
},
{
name: "unexpected file path '../a.yaml'",
header: &tar.Header{
Name: "../a.yaml",
Typeflag: tar.TypeReg,
},
expectedErr: fmt.Errorf("the given file contains unexpected file path: %s", "../a.yaml"),
},
{
name: "expected file path 'crds/a.yaml'",
header: &tar.Header{
Name: "crds/a.yaml",
Typeflag: tar.TypeReg,
},
expectedErr: nil,
},
}
for _, item := range testItems {
assert.Equal(t, item.expectedErr, ValidateCrdsTarBall(item.header))
}
}