Skip to content

Commit

Permalink
UPSTREAM: <carry>: add icsp,idms,itms validation reject creating icsp…
Browse files Browse the repository at this point in the history
… 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
QiWang19 committed Feb 6, 2023
1 parent 85e5e7f commit da62781
Show file tree
Hide file tree
Showing 7 changed files with 848 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package customresourcevalidationregistration
import (
"k8s.io/apiserver/pkg/admission"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/apirequestcount"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/imagecontentsourcepolicy"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/imagedigestmirrorset"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/imagetagmirrorset"

"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/apiserver"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/authentication"
Expand Down Expand Up @@ -31,6 +34,9 @@ var AllCustomResourceValidators = []string{
console.PluginName,
dns.PluginName,
image.PluginName,
imagecontentsourcepolicy.PluginName,
imagedigestmirrorset.PluginName,
imagetagmirrorset.PluginName,
oauth.PluginName,
project.PluginName,
config.PluginName,
Expand Down Expand Up @@ -58,6 +64,9 @@ func RegisterCustomResourceValidation(plugins *admission.Plugins) {
console.Register(plugins)
dns.Register(plugins)
image.Register(plugins)
imagecontentsourcepolicy.Register(plugins)
imagedigestmirrorset.Register(plugins)
imagetagmirrorset.Register(plugins)
oauth.Register(plugins)
project.Register(plugins)
config.Register(plugins)
Expand Down
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
}
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))
})
}
}
Loading

0 comments on commit da62781

Please sign in to comment.