Skip to content

Commit

Permalink
api: linodemachine: add create validation
Browse files Browse the repository at this point in the history
  • Loading branch information
cbang-akamai committed Apr 29, 2024
1 parent b29fa1b commit c29b68b
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 7 deletions.
142 changes: 136 additions & 6 deletions api/v1alpha1/linodemachine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,29 @@ limitations under the License.
package v1alpha1

import (
"context"
"fmt"
"net/http"
"slices"

"github.com/linode/linodego"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

var (
// The list of valid SCSI device paths that user-defined data disks may attach to
// NOTE: sda is reserved for the OS disk
LinodeMachineSCSIPaths = []string{"sdb", "sdc", "sdd", "sde", "sdh"}
)

// log is for logging in this package.
var linodemachinelog = logf.Log.WithName("linodemachine-resource")

Expand All @@ -34,19 +50,15 @@ func (r *LinodeMachine) SetupWebhookWithManager(mgr ctrl.Manager) error {
Complete()
}

// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
//+kubebuilder:webhook:path=/validate-infrastructure-cluster-x-k8s-io-v1alpha1-linodemachine,mutating=false,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=linodemachines,verbs=create;update,versions=v1alpha1,name=vlinodemachine.kb.io,admissionReviewVersions=v1
//+kubebuilder:webhook:path=/validate-infrastructure-cluster-x-k8s-io-v1alpha1-linodemachine,mutating=false,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=linodemachines,verbs=create,versions=v1alpha1,name=vlinodemachine.kb.io,admissionReviewVersions=v1

var _ webhook.Validator = &LinodeMachine{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *LinodeMachine) ValidateCreate() (admission.Warnings, error) {
linodemachinelog.Info("validate create", "name", r.Name)

// TODO(user): fill in your validation logic upon object creation.
return nil, nil
return nil, r.validateLinodeMachine()
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
Expand All @@ -64,3 +76,121 @@ func (r *LinodeMachine) ValidateDelete() (admission.Warnings, error) {
// TODO(user): fill in your validation logic upon object deletion.
return nil, nil
}

func (r *LinodeMachine) validateLinodeMachine() error {
var errs field.ErrorList
if err := r.validateLinodeMachineSpec(); err != nil {
errs = slices.Concat(errs, err)
}

if len(errs) == 0 {
return nil
}
return apierrors.NewInvalid(
schema.GroupKind{Group: "infrastructure.cluster.x-k8s.io", Kind: "LinodeMachine"},
r.Name, errs)
}

func (r *LinodeMachine) validateLinodeMachineSpec() field.ErrorList {
var (
errs field.ErrorList
client = linodego.NewClient(http.DefaultClient)
ctx = context.TODO()
)
if err := validateRegion(ctx, client, r.Spec.Region, field.NewPath("spec").Child("region")); err != nil {
errs = append(errs, err)
}
plan, err := validateLinodeType(ctx, client, r.Spec.Type, field.NewPath("spec").Child("type"))
if err != nil {
errs = append(errs, err)
}
if err := r.validateLinodeMachineDisks(plan); err != nil {
errs = append(errs, err)
}

if len(errs) == 0 {
return nil
}
return errs
}

func validateRegion(ctx context.Context, client linodego.Client, region string, path *field.Path) *field.Error {
if _, err := client.GetRegion(ctx, region); err != nil {
return field.NotFound(path, region)
}

return nil
}

func validateLinodeType(ctx context.Context, client linodego.Client, plan string, path *field.Path) (*linodego.LinodeType, *field.Error) {
linodeType, err := client.GetType(ctx, plan)
if err != nil {
return nil, field.NotFound(path, plan)
}

return linodeType, nil
}

func (r *LinodeMachine) validateLinodeMachineDisks(plan *linodego.LinodeType) *field.Error {
// The Linode plan information is required to perform disk validation
if plan == nil {
return nil
}

var (
// The Linode API represents storage sizes in megabytes (MB)
// See: https://www.linode.com/docs/api/linode-types/#type-view
planSize = resource.MustParse(fmt.Sprintf("%d%s", plan.Disk, "M"))
remainSize = &planSize
err *field.Error
)

if remainSize, err = validateDisk(r.Spec.OSDisk, field.NewPath("spec").Child("osDisk"), remainSize); err != nil {
return err
}
if _, err := validateDataDisks(r.Spec.DataDisks, field.NewPath("spec").Child("dataDisks"), remainSize); err != nil {
return err
}

return nil
}

func validateDataDisks(disks map[string]*InstanceDisk, path *field.Path, availSize *resource.Quantity) (*resource.Quantity, *field.Error) {
var (
devs []string
remainSize = availSize
)

for dev, disk := range disks {
if !slices.Contains(LinodeMachineSCSIPaths, dev) {
return nil, field.Forbidden(path.Child(dev), fmt.Sprintf("allowed device paths: %v", LinodeMachineSCSIPaths))
}
if slices.Contains(devs, dev) {
return nil, field.Duplicate(path.Child(dev), "duplicate device path")
}
devs = append(devs, dev)

var err *field.Error
if remainSize, err = validateDisk(disk, path.Child(dev), remainSize); err != nil {
return nil, err
}
}
return remainSize, nil
}

func validateDisk(disk *InstanceDisk, path *field.Path, availSize *resource.Quantity) (*resource.Quantity, *field.Error) {
if disk == nil {
return availSize, nil
}

if disk.Size.Sign() == -1 {
return nil, field.Invalid(path, disk.Size.String(), "cannot be negative")
}
if availSize.Cmp(disk.Size) == -1 {
return nil, field.Invalid(path, disk.Size.String(), "sum of disk sizes cannot exceed plan storage")
}

// Decrement the remaining amount of space available
availSize.Sub(disk.Size)
return availSize, nil
}
1 change: 0 additions & 1 deletion config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ webhooks:
- v1alpha1
operations:
- CREATE
- UPDATE
resources:
- linodemachines
sideEffects: None

0 comments on commit c29b68b

Please sign in to comment.