forked from kubernetes/kubernetes
-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
UPSTREAM: <carry>: add icsp,idms,itms validation reject creating icsp…
… with idms/itms exist Reject icsp with idms.itms resources exists. According to the discuusion resolution https://docs.google.com/document/d/13h6IJn8wlzXdiPMvCWlMEHOXXqEZ9_GYOl02Wldb3z8/edit?usp=sharing, one of current icsp or new mirror setting crd should be rejected if a user tries to use them on the same cluster. Signed-off-by: Qi Wang <qiwan@redhat.com>
- Loading branch information
Showing
7 changed files
with
848 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
...on/customresourcevalidation/imagecontentsourcepolicy/validate_imagecontentsourcepolicy.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package imagecontentsourcepolicy | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
|
||
operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1" | ||
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" | ||
"github.com/openshift/library-go/pkg/apiserver/admission/admissionrestconfig" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||
"k8s.io/apimachinery/pkg/util/validation/field" | ||
"k8s.io/apiserver/pkg/admission" | ||
"k8s.io/client-go/rest" | ||
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation" | ||
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/imagedigestmirrorset" | ||
) | ||
|
||
const PluginName = "operator.openshift.io/ValidateImageContentSourcePolicy" | ||
|
||
// Register registers a plugin | ||
func Register(plugins *admission.Plugins) { | ||
|
||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { | ||
return newValidateICSP() | ||
}) | ||
} | ||
|
||
type validateCustomResourceWithClient struct { | ||
admission.ValidationInterface | ||
|
||
imageDigestMirrorSetsGetter configv1client.ImageDigestMirrorSetsGetter | ||
imageTagMirrorSetsGetter configv1client.ImageTagMirrorSetsGetter | ||
} | ||
|
||
func newValidateICSP() (admission.Interface, error) { | ||
ret := &validateCustomResourceWithClient{} | ||
|
||
delegate, err := customresourcevalidation.NewValidator( | ||
map[schema.GroupResource]bool{ | ||
operatorv1alpha1.Resource(imagedigestmirrorset.ICSPResource): true, | ||
}, | ||
map[schema.GroupVersionKind]customresourcevalidation.ObjectValidator{ | ||
operatorv1alpha1.GroupVersion.WithKind(imagedigestmirrorset.ICSPKind): imagecontentsourcepolicy{imageDigestMirrorSetsGetter: ret.getImageDigestMirrorSetsGetter, imageTagMirrorSetsGetter: ret.getImageTagMirrorSetsGetter}, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
ret.ValidationInterface = delegate | ||
return ret, nil | ||
} | ||
|
||
var _ admissionrestconfig.WantsRESTClientConfig = &validateCustomResourceWithClient{} | ||
|
||
func (i *validateCustomResourceWithClient) getImageDigestMirrorSetsGetter() configv1client.ImageDigestMirrorSetsGetter { | ||
return i.imageDigestMirrorSetsGetter | ||
} | ||
|
||
func (i *validateCustomResourceWithClient) getImageTagMirrorSetsGetter() configv1client.ImageTagMirrorSetsGetter { | ||
return i.imageTagMirrorSetsGetter | ||
} | ||
|
||
func (i *validateCustomResourceWithClient) SetRESTClientConfig(restClientConfig rest.Config) { | ||
client, err := configv1client.NewForConfig(&restClientConfig) | ||
if err != nil { | ||
utilruntime.HandleError(err) | ||
return | ||
} | ||
i.imageDigestMirrorSetsGetter = client | ||
i.imageTagMirrorSetsGetter = client | ||
} | ||
|
||
func (i *validateCustomResourceWithClient) ValidateInitialization() error { | ||
if i.imageDigestMirrorSetsGetter == nil { | ||
return fmt.Errorf(PluginName + " needs an imageDigestMirrorSetsGetter") | ||
} | ||
|
||
if i.imageTagMirrorSetsGetter == nil { | ||
return fmt.Errorf(PluginName + " needs an imageTagMirrorSetsGetter") | ||
} | ||
|
||
if initializationValidator, ok := i.ValidationInterface.(admission.InitializationValidator); ok { | ||
return initializationValidator.ValidateInitialization() | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type imagecontentsourcepolicy struct { | ||
imageDigestMirrorSetsGetter func() configv1client.ImageDigestMirrorSetsGetter | ||
imageTagMirrorSetsGetter func() configv1client.ImageTagMirrorSetsGetter | ||
} | ||
|
||
func (i imagecontentsourcepolicy) ValidateCreate(uncastObj runtime.Object) field.ErrorList { | ||
return i.validateICSPUse("create") | ||
} | ||
|
||
func (i imagecontentsourcepolicy) ValidateUpdate(uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList { | ||
return i.validateICSPUse("update") | ||
} | ||
|
||
func (i imagecontentsourcepolicy) ValidateStatusUpdate(uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList { | ||
return i.validateICSPUse("update") | ||
} | ||
|
||
func (i imagecontentsourcepolicy) validateICSPUse(action string) field.ErrorList { | ||
idmsList, err := i.imageDigestMirrorSetsGetter().ImageDigestMirrorSets().List(context.TODO(), metav1.ListOptions{}) | ||
if err != nil { | ||
return field.ErrorList{field.InternalError(field.NewPath("Kind", imagedigestmirrorset.IDMSKind), err)} | ||
} | ||
if len(idmsList.Items) > 0 { | ||
return field.ErrorList{ | ||
field.Forbidden(field.NewPath("Kind", imagedigestmirrorset.ICSPKind), fmt.Sprintf("can't %s %s when %s resources exist", action, imagedigestmirrorset.ICSPKind, imagedigestmirrorset.IDMSKind))} | ||
} | ||
itmsList, err := i.imageTagMirrorSetsGetter().ImageTagMirrorSets().List(context.TODO(), metav1.ListOptions{}) | ||
if err != nil { | ||
return field.ErrorList{field.InternalError(field.NewPath("Kind", imagedigestmirrorset.ITMSKind), err)} | ||
} | ||
if len(itmsList.Items) > 0 { | ||
return field.ErrorList{ | ||
field.Forbidden(field.NewPath("Kind", imagedigestmirrorset.ICSPKind), fmt.Sprintf("can't %s %s when %s resources exist", action, imagedigestmirrorset.ICSPKind, imagedigestmirrorset.ITMSKind))} | ||
} | ||
return nil | ||
} |
154 changes: 154 additions & 0 deletions
154
...stomresourcevalidation/imagecontentsourcepolicy/validate_imagecontentsourcepolicy_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package imagecontentsourcepolicy | ||
|
||
import ( | ||
"testing" | ||
|
||
configv1 "github.com/openshift/api/config/v1" | ||
operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1" | ||
configclientfake "github.com/openshift/client-go/config/clientset/versioned/fake" | ||
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" | ||
operatorclientfake "github.com/openshift/client-go/operator/clientset/versioned/fake" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
runtime "k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/util/validation/field" | ||
) | ||
|
||
func TestValidateICSPUse(t *testing.T) { | ||
|
||
for _, tt := range []struct { | ||
name string | ||
objects []runtime.Object | ||
icsps []runtime.Object | ||
action string | ||
validateErrors func(t *testing.T, errs field.ErrorList) | ||
}{ | ||
{ | ||
name: "can't create icsp with existing idms", | ||
objects: []runtime.Object{ | ||
&configv1.ImageDigestMirrorSet{ | ||
ObjectMeta: metav1.ObjectMeta{Name: "idms"}, | ||
Spec: configv1.ImageDigestMirrorSetSpec{ | ||
ImageDigestMirrors: []configv1.ImageDigestMirrors{ | ||
{Source: "example.com", Mirrors: []configv1.ImageMirror{"mirror.com"}}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
action: "create", | ||
|
||
validateErrors: func(t *testing.T, errs field.ErrorList) { | ||
t.Helper() | ||
if len(errs) != 1 { | ||
t.Fatal(errs) | ||
} | ||
if errs[0].Error() != `Kind.ImageContentSourcePolicy: Forbidden: can't create ImageContentSourcePolicy when ImageDigestMirrorSet resources exist` { | ||
t.Error(errs[0]) | ||
} | ||
}, | ||
}, | ||
{ | ||
name: "can't create icsp with existing itms", | ||
objects: []runtime.Object{ | ||
&configv1.ImageTagMirrorSet{ | ||
ObjectMeta: metav1.ObjectMeta{Name: "itms"}, | ||
Spec: configv1.ImageTagMirrorSetSpec{ | ||
ImageTagMirrors: []configv1.ImageTagMirrors{ | ||
{Source: "example.com", Mirrors: []configv1.ImageMirror{"mirror.com"}}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
action: "create", | ||
|
||
validateErrors: func(t *testing.T, errs field.ErrorList) { | ||
t.Helper() | ||
if len(errs) != 1 { | ||
t.Fatal(errs) | ||
} | ||
if errs[0].Error() != `Kind.ImageContentSourcePolicy: Forbidden: can't create ImageContentSourcePolicy when ImageTagMirrorSet resources exist` { | ||
t.Error(errs[0]) | ||
} | ||
}, | ||
}, | ||
{ | ||
name: "success creating icsp", | ||
action: "create", | ||
|
||
validateErrors: func(t *testing.T, errs field.ErrorList) { | ||
t.Helper() | ||
if len(errs) > 0 { | ||
t.Fatal(errs) | ||
} | ||
}, | ||
}, | ||
{ | ||
name: "update existing icsp", | ||
icsps: []runtime.Object{ | ||
&operatorv1alpha1.ImageContentSourcePolicy{ | ||
ObjectMeta: metav1.ObjectMeta{Name: "icsp"}, | ||
Spec: operatorv1alpha1.ImageContentSourcePolicySpec{ | ||
RepositoryDigestMirrors: []operatorv1alpha1.RepositoryDigestMirrors{ | ||
{Source: "example.com", Mirrors: []string{"mirror.com"}}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
action: "update", | ||
validateErrors: func(t *testing.T, errs field.ErrorList) { | ||
t.Helper() | ||
if len(errs) > 0 { | ||
t.Fatal(errs) | ||
} | ||
}, | ||
}, | ||
{ | ||
// this case is technically impossible, we prohibit to have icsp to be updated when there is idms | ||
name: "update existing icsp with idms", | ||
objects: []runtime.Object{ | ||
&configv1.ImageDigestMirrorSet{ | ||
ObjectMeta: metav1.ObjectMeta{Name: "idms"}, | ||
Spec: configv1.ImageDigestMirrorSetSpec{ | ||
ImageDigestMirrors: []configv1.ImageDigestMirrors{ | ||
{Source: "example.com", Mirrors: []configv1.ImageMirror{"mirror.com"}}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
icsps: []runtime.Object{ | ||
&operatorv1alpha1.ImageContentSourcePolicy{ | ||
ObjectMeta: metav1.ObjectMeta{Name: "icsp"}, | ||
Spec: operatorv1alpha1.ImageContentSourcePolicySpec{ | ||
RepositoryDigestMirrors: []operatorv1alpha1.RepositoryDigestMirrors{ | ||
{Source: "example.com", Mirrors: []string{"mirror.com"}}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
action: "update", | ||
|
||
validateErrors: func(t *testing.T, errs field.ErrorList) { | ||
t.Helper() | ||
if len(errs) != 1 { | ||
t.Fatal(errs) | ||
} | ||
if errs[0].Error() != `Kind.ImageContentSourcePolicy: Forbidden: can't update ImageContentSourcePolicy when ImageDigestMirrorSet resources exist` { | ||
t.Error(errs[0]) | ||
} | ||
}, | ||
}, | ||
} { | ||
t.Run(tt.name, func(t *testing.T) { | ||
fakeclient := configclientfake.NewSimpleClientset(tt.objects...) | ||
_ = operatorclientfake.NewSimpleClientset(tt.icsps...) | ||
instance := imagecontentsourcepolicy{ | ||
imageDigestMirrorSetsGetter: func() configv1client.ImageDigestMirrorSetsGetter { | ||
return fakeclient.ConfigV1() | ||
}, | ||
imageTagMirrorSetsGetter: func() configv1client.ImageTagMirrorSetsGetter { | ||
return fakeclient.ConfigV1() | ||
}, | ||
} | ||
tt.validateErrors(t, instance.validateICSPUse(tt.action)) | ||
}) | ||
} | ||
} |
Oops, something went wrong.