Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions pkg/admission/customresourcevalidation/attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package customresourcevalidation

import (
configv1 "github.com/openshift/api/config/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission"
)

// unstructuredUnpackingAttributes tries to convert to a real object in the config scheme
type unstructuredUnpackingAttributes struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this wrapper struct? An explicit conversion in the admission handler would be much simpler. No need for on-the-fly conversion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this wrapper struct? An explicit conversion in the admission handler would be much simpler. No need for on-the-fly conversion.

We don't know whether or not we can until point of use. Also, point of use makes sure that that we don't miss a spot.

admission.Attributes
}

func (a *unstructuredUnpackingAttributes) GetObject() runtime.Object {
return toBestObjectPossible(a.Attributes.GetObject())
}

func (a *unstructuredUnpackingAttributes) GetOldObject() runtime.Object {
return toBestObjectPossible(a.Attributes.GetOldObject())
}

// toBestObjectPossible tries to convert to a real object in the config scheme
func toBestObjectPossible(orig runtime.Object) runtime.Object {
Copy link
Contributor

@smarterclayton smarterclayton Jan 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function really could just be a shared helper that takes the unstructured and the into and then does the type check, verifies the config scheme allows into, converts, and optionally returns invalid errors. That bypasses the cast and make this much easier to read.

var image *configv1.Image
if errs := ConvertFromUnstructured(obj, image); len(errs) > 0 {
  return errs
}
...

instead of having to do the very ugly cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function really could just be a shared helper that takes the unstructured and the into

The into isn't known, that's part of what this function is figuring out. If it gets specified then I have reflection trying to clone it. Also the unstructured has to be cast somewhere.

unstructuredOrig, ok := orig.(runtime.Unstructured)
if !ok {
return orig
}

targetObj, err := configScheme.New(unstructuredOrig.GetObjectKind().GroupVersionKind())
if err != nil {
utilruntime.HandleError(err)
return unstructuredOrig
}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredOrig.UnstructuredContent(), targetObj); err != nil {
utilruntime.HandleError(err)
return unstructuredOrig
}
return targetObj
}

var configScheme = runtime.NewScheme()

func init() {
utilruntime.Must(configv1.Install(configScheme))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package customresourcevalidationregistration

import (
"github.com/openshift/origin/pkg/admission/customresourcevalidation/image"
"k8s.io/apiserver/pkg/admission"
)

// AllCustomResourceValidators are the names of all custom resource validators that should be registered
var AllCustomResourceValidators = []string{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

godoc. what are these strings?

"config.openshift.io/ValidateImage",
}

func RegisterCustomResourceValidation(plugins *admission.Plugins) {
image.Register(plugins)
}
97 changes: 97 additions & 0 deletions pkg/admission/customresourcevalidation/customresourcevalidator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package customresourcevalidation

import (
"fmt"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"
)

type ObjectValidator interface {
ValidateCreate(obj runtime.Object) field.ErrorList
ValidateUpdate(obj runtime.Object, oldObj runtime.Object) field.ErrorList
ValidateStatusUpdate(obj runtime.Object, oldObj runtime.Object) field.ErrorList
}

// ValidateCustomResource is an implementation of admission.Interface.
// It looks at all new pods and overrides each container's image pull policy to Always.
type validateCustomResource struct {
*admission.Handler

resources map[schema.GroupResource]bool
validators map[schema.GroupVersionKind]ObjectValidator
}

func NewValidator(resources map[schema.GroupResource]bool, validators map[schema.GroupVersionKind]ObjectValidator) (admission.Interface, error) {
return &validateCustomResource{
Handler: admission.NewHandler(admission.Create, admission.Update),
resources: resources,
validators: validators,
}, nil
}

var _ admission.ValidationInterface = &validateCustomResource{}

// Validate is an admission function that will validate a CRD in config.openshift.io. uncastAttributes are attributes
// that are of type unstructured.
func (a *validateCustomResource) Validate(uncastAttributes admission.Attributes) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Godoc. What ae uncastAttributes? Attributes with unstructured old+new object inside?

attributes := &unstructuredUnpackingAttributes{Attributes: uncastAttributes}
if a.shouldIgnore(attributes) {
return nil
}
validator, ok := a.validators[attributes.GetKind()]
if !ok {
return admission.NewForbidden(attributes, fmt.Errorf("unhandled kind: %v", attributes.GetKind()))
}

switch attributes.GetOperation() {
case admission.Create:
// creating subresources isn't something we understand, but we can be pretty sure we don't need to validate it
if len(attributes.GetSubresource()) > 0 {
return nil
}
errors := validator.ValidateCreate(attributes.GetObject())
if len(errors) == 0 {
return nil
}
return apierrors.NewInvalid(attributes.GetKind().GroupKind(), attributes.GetName(), errors)

case admission.Update:
switch attributes.GetSubresource() {
case "":
errors := validator.ValidateUpdate(attributes.GetObject(), attributes.GetOldObject())
if len(errors) == 0 {
return nil
}
return apierrors.NewInvalid(attributes.GetKind().GroupKind(), attributes.GetName(), errors)

case "status":
errors := validator.ValidateStatusUpdate(attributes.GetObject(), attributes.GetOldObject())
if len(errors) == 0 {
return nil
}
return apierrors.NewInvalid(attributes.GetKind().GroupKind(), attributes.GetName(), errors)

default:
return admission.NewForbidden(attributes, fmt.Errorf("unhandled subresource: %v", attributes.GetSubresource()))
}

default:
return admission.NewForbidden(attributes, fmt.Errorf("unhandled operation: %v", attributes.GetOperation()))
}
}

func (a *validateCustomResource) shouldIgnore(attributes admission.Attributes) bool {
if !a.resources[attributes.GetResource().GroupResource()] {
return true
}
// if a subresource is specified and it isn't status, skip it
if len(attributes.GetSubresource()) > 0 && attributes.GetSubresource() != "status" {
return true
}

return false
}
Loading