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
15 changes: 4 additions & 11 deletions internal/webhook/v1alpha2/linodecluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ func SetupLinodeClusterWebhookWithManager(mgr ctrl.Manager) error {
Complete()
}

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

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
Expand All @@ -59,19 +58,13 @@ func (r *linodeClusterValidator) ValidateCreate(ctx context.Context, obj runtime
spec := cluster.Spec
linodeclusterlog.Info("validate create", "name", cluster.Name)

var linodeclient clients.LinodeClient = defaultLinodeClient
skipAPIValidation := false
skipAPIValidation, linodeClient := setupClientWithCredentials(ctx, r.Client, spec.CredentialsRef,
cluster.Name, cluster.GetNamespace(), linodeclusterlog)

// Handle credentials if provided
if spec.CredentialsRef != nil {
skipAPIValidation, linodeclient = setupClientWithCredentials(ctx, r.Client, spec.CredentialsRef,
cluster.Name, cluster.GetNamespace(), linodeclusterlog)
}

// TODO: instrument with tracing, might need refactor to preserve readibility
// TODO: instrument with tracing, might need refactor to preserve readability
var errs field.ErrorList

if err := r.validateLinodeClusterSpec(ctx, linodeclient, spec, skipAPIValidation); err != nil {
if err := r.validateLinodeClusterSpec(ctx, linodeClient, spec, skipAPIValidation); err != nil {
errs = slices.Concat(errs, err)
}

Expand Down
2 changes: 0 additions & 2 deletions internal/webhook/v1alpha2/linodeclustertemplate_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,3 @@ func SetupLinodeClusterTemplateWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).For(&infrav1alpha2.LinodeClusterTemplate{}).
Complete()
}

// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
14 changes: 3 additions & 11 deletions internal/webhook/v1alpha2/linodemachine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,11 @@ func SetupLinodeMachineWebhookWithManager(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 update and deletion validation.
// +kubebuilder:webhook:path=/validate-infrastructure-cluster-x-k8s-io-v1alpha2-linodemachine,mutating=false,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=linodemachines,verbs=create,versions=v1alpha2,name=validation.linodemachine.infrastructure.cluster.x-k8s.io,admissionReviewVersions=v1

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *linodeMachineValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
var linodeclient clients.LinodeClient = defaultLinodeClient
var errs field.ErrorList
skipAPIValidation := false

machine, ok := obj.(*infrav1alpha2.LinodeMachine)
if !ok {
Expand All @@ -83,13 +78,10 @@ func (r *linodeMachineValidator) ValidateCreate(ctx context.Context, obj runtime
spec := machine.Spec
linodemachinelog.Info("validate create", "name", machine.Name)

// Handle credentials if provided
if spec.CredentialsRef != nil {
skipAPIValidation, linodeclient = setupClientWithCredentials(ctx, r.Client, spec.CredentialsRef,
machine.Name, machine.GetNamespace(), linodemachinelog)
}
skipAPIValidation, linodeClient := setupClientWithCredentials(ctx, r.Client, spec.CredentialsRef,
machine.Name, machine.GetNamespace(), linodemachinelog)

if err := r.validateLinodeMachineSpec(ctx, linodeclient, spec, skipAPIValidation); err != nil {
if err := r.validateLinodeMachineSpec(ctx, linodeClient, spec, skipAPIValidation); err != nil {
errs = slices.Concat(errs, err)
}

Expand Down
24 changes: 15 additions & 9 deletions internal/webhook/v1alpha2/linodeobjectstoragebucket_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
Expand All @@ -48,7 +49,9 @@ func SetupLinodeObjectStorageBucketWebhookWithManager(mgr ctrl.Manager) error {
// +kubebuilder:webhook:path=/validate-infrastructure-cluster-x-k8s-io-v1alpha2-linodeobjectstoragebucket,mutating=false,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=linodeobjectstoragebuckets,verbs=create;update,versions=v1alpha2,name=validation.linodeobjectstoragebucket.infrastructure.cluster.x-k8s.io,admissionReviewVersions=v1

// LinodeObjectStorageBucketCustomValidator struct is responsible for validating the LinodeObjectStorageBucket resource
type LinodeObjectStorageBucketCustomValidator struct{}
type LinodeObjectStorageBucketCustomValidator struct {
Client client.Client
}

var _ webhook.CustomValidator = &LinodeObjectStorageBucketCustomValidator{}

Expand All @@ -59,8 +62,9 @@ func (v *LinodeObjectStorageBucketCustomValidator) ValidateCreate(ctx context.Co
return nil, fmt.Errorf("expected a LinodeObjectStorageBucket object but got %T", obj)
}
linodeobjectstoragebucketlog.Info("validate create", "name", bucket.Name)

return nil, v.validateLinodeObjectStorageBucket(ctx, bucket, &defaultLinodeClient)
skipAPIValidation, linodeClient := setupClientWithCredentials(ctx, v.Client, bucket.Spec.CredentialsRef,
bucket.Name, bucket.GetNamespace(), linodemachinelog)
return nil, v.validateLinodeObjectStorageBucket(ctx, bucket, linodeClient, skipAPIValidation)
}

// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type LinodeObjectStorageBucket.
Expand All @@ -71,7 +75,7 @@ func (v *LinodeObjectStorageBucketCustomValidator) ValidateUpdate(ctx context.Co
}
linodeobjectstoragebucketlog.Info("validate update", "name", bucket.Name)

return nil, v.validateLinodeObjectStorageBucket(ctx, bucket, &defaultLinodeClient)
return nil, nil
}

// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type LinodeObjectStorageBucket.
Expand All @@ -86,10 +90,10 @@ func (v *LinodeObjectStorageBucketCustomValidator) ValidateDelete(ctx context.Co
return nil, nil
}

func (v *LinodeObjectStorageBucketCustomValidator) validateLinodeObjectStorageBucket(ctx context.Context, bucket *infrav1alpha2.LinodeObjectStorageBucket, client clients.LinodeClient) error {
func (v *LinodeObjectStorageBucketCustomValidator) validateLinodeObjectStorageBucket(ctx context.Context, bucket *infrav1alpha2.LinodeObjectStorageBucket, linodeClient clients.LinodeClient, skipAPIValidation bool) error {
var errs field.ErrorList

if err := v.validateLinodeObjectStorageBucketSpec(ctx, bucket, client); err != nil {
if err := v.validateLinodeObjectStorageBucketSpec(ctx, bucket, linodeClient, skipAPIValidation); err != nil {
errs = slices.Concat(errs, err)
}

Expand All @@ -101,10 +105,12 @@ func (v *LinodeObjectStorageBucketCustomValidator) validateLinodeObjectStorageBu
bucket.Name, errs)
}

func (v *LinodeObjectStorageBucketCustomValidator) validateLinodeObjectStorageBucketSpec(ctx context.Context, bucket *infrav1alpha2.LinodeObjectStorageBucket, client clients.LinodeClient) field.ErrorList {
func (v *LinodeObjectStorageBucketCustomValidator) validateLinodeObjectStorageBucketSpec(ctx context.Context, bucket *infrav1alpha2.LinodeObjectStorageBucket, linodeClient clients.LinodeClient, skipAPIValidation bool) field.ErrorList {
var errs field.ErrorList

if err := validateObjectStorageRegion(ctx, client, bucket.Spec.Region, field.NewPath("spec").Child("region")); err != nil {
if skipAPIValidation {
return errs
}
if err := validateObjectStorageRegion(ctx, linodeClient, bucket.Spec.Region, field.NewPath("spec").Child("region")); err != nil {
errs = append(errs, err)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func TestValidateLinodeObjectStorageBucket(t *testing.T) {
Result("success", func(ctx context.Context, mck Mock) {
bucket := bucket
bucket.Spec.Region = "iad"
assert.NoError(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient))
assert.NoError(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient, true))
}),
),
Path(
Expand All @@ -75,7 +75,7 @@ func TestValidateLinodeObjectStorageBucket(t *testing.T) {
Result("success", func(ctx context.Context, mck Mock) {
bucket := bucket
bucket.Spec.Region = "us-iad"
assert.NoError(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient))
assert.NoError(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient, true))
}),
),
Path(
Expand All @@ -87,7 +87,7 @@ func TestValidateLinodeObjectStorageBucket(t *testing.T) {
Result("success", func(ctx context.Context, mck Mock) {
bucket := bucket
bucket.Spec.Region = "us-iad-1"
assert.NoError(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient))
assert.NoError(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient, true))
}),
),
),
Expand All @@ -98,7 +98,7 @@ func TestValidateLinodeObjectStorageBucket(t *testing.T) {
Result("error", func(ctx context.Context, mck Mock) {
bucket := bucket
bucket.Spec.Region = "123invalid"
assert.Error(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient))
assert.Error(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient, false))
}),
),
Path(
Expand All @@ -107,7 +107,7 @@ func TestValidateLinodeObjectStorageBucket(t *testing.T) {
Result("error", func(ctx context.Context, mck Mock) {
bucket := bucket
bucket.Spec.Region = "invalid-2-2"
assert.Error(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient))
assert.Error(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient, false))
}),
),
Path(
Expand All @@ -117,7 +117,7 @@ func TestValidateLinodeObjectStorageBucket(t *testing.T) {
Result("error", func(ctx context.Context, mck Mock) {
bucket := bucket
bucket.Spec.Region = "us-1"
assert.Error(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient))
assert.Error(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient, false))
}),
),
Path(
Expand All @@ -127,7 +127,7 @@ func TestValidateLinodeObjectStorageBucket(t *testing.T) {
mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(&region, nil).AnyTimes()
}),
Result("error", func(ctx context.Context, mck Mock) {
assert.Error(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient))
assert.Error(t, objvalidator.validateLinodeObjectStorageBucket(ctx, &bucket, mck.LinodeClient, false))
}),
),
),
Expand Down
12 changes: 3 additions & 9 deletions internal/webhook/v1alpha2/linodeplacementgroup_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,12 @@ func (v *LinodePlacementGroupCustomValidator) ValidateCreate(ctx context.Context
}
linodeplacementgrouplog.Info("Validation for LinodePlacementGroup upon creation", "name", pg.GetName())

var linodeclient clients.LinodeClient = defaultLinodeClient
skipAPIValidation := false

// Handle credentials if provided
if pg.Spec.CredentialsRef != nil {
skipAPIValidation, linodeclient = setupClientWithCredentials(ctx, v.Client, pg.Spec.CredentialsRef,
pg.Name, pg.GetNamespace(), linodeplacementgrouplog)
}
skipAPIValidation, linodeClient := setupClientWithCredentials(ctx, v.Client, pg.Spec.CredentialsRef,
pg.Name, pg.GetNamespace(), linodeplacementgrouplog)

var errs field.ErrorList

if err := v.validateLinodePlacementGroupSpec(ctx, linodeclient, pg.Spec, pg.Name, skipAPIValidation); err != nil {
if err := v.validateLinodePlacementGroupSpec(ctx, linodeClient, pg.Spec, pg.Name, skipAPIValidation); err != nil {
errs = slices.Concat(errs, err)
}

Expand Down
15 changes: 4 additions & 11 deletions internal/webhook/v1alpha2/linodevpc_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ func SetupLinodeVPCWebhookWithManager(mgr ctrl.Manager) error {
Complete()
}

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

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
Expand All @@ -97,17 +96,11 @@ func (r *linodeVPCValidator) ValidateCreate(ctx context.Context, obj runtime.Obj
spec := vpc.Spec
linodevpclog.Info("validate create", "name", vpc.Name)

var linodeclient clients.LinodeClient = defaultLinodeClient
skipAPIValidation := false
skipAPIValidation, linodeClient := setupClientWithCredentials(ctx, r.Client, spec.CredentialsRef,
vpc.Name, vpc.GetNamespace(), linodevpclog)

// Handle credentials if provided
if spec.CredentialsRef != nil {
skipAPIValidation, linodeclient = setupClientWithCredentials(ctx, r.Client, spec.CredentialsRef,
vpc.Name, vpc.GetNamespace(), linodevpclog)
}

// TODO: instrument with tracing, might need refactor to preserve readibility
errs := r.validateLinodeVPCSpec(ctx, linodeclient, spec, skipAPIValidation)
// TODO: instrument with tracing, might need refactor to preserve readability
errs := r.validateLinodeVPCSpec(ctx, linodeClient, spec, skipAPIValidation)

if len(errs) == 0 {
return nil, nil
Expand Down
27 changes: 15 additions & 12 deletions internal/webhook/v1alpha2/webhook_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"
"net/http"
"os"
"regexp"
"slices"
"time"
Expand All @@ -41,14 +42,6 @@ const (
defaultClientTimeout = time.Second * 10
)

var (
// defaultLinodeClient is an unauthenticated Linode client
defaultLinodeClient = linodeclient.NewLinodeClientWithTracing(
ptr.To(linodego.NewClient(&http.Client{Timeout: defaultClientTimeout})),
linodeclient.DefaultDecorator(),
)
)

func validateRegion(ctx context.Context, linodegoclient clients.LinodeClient, id string, path *field.Path, capabilities ...string) *field.Error {
region, err := linodegoclient.GetRegion(ctx, id)
if err != nil {
Expand Down Expand Up @@ -132,14 +125,24 @@ func getCredentials(ctx context.Context, crClient clients.K8sClient, credentials
return &credSecret, nil
}

// setupClientWithCredentials configures a Linode client with credentials from a secret reference
// setupClientWithCredentials configures a Linode client with credentials the LINODE_TOKEN env variable or
// a secret reference if it is provided
// Returns (skipAPIValidation, client) - skipAPIValidation will be true if credentials cannot be found
// and API validation should be skipped
func setupClientWithCredentials(ctx context.Context, crClient clients.K8sClient, credRef *corev1.SecretReference,
resourceName, namespace string, logger logr.Logger) (bool, clients.LinodeClient) {
linodeClient := defaultLinodeClient
linodeClient := linodeclient.NewLinodeClientWithTracing(
ptr.To(linodego.NewClient(&http.Client{Timeout: defaultClientTimeout})),
linodeclient.DefaultDecorator(),
)
credName := ""
apiToken := []byte(os.Getenv("LINODE_TOKEN"))
var err error
if credRef != nil {
credName = credRef.Name
apiToken, err = getCredentialDataFromRef(ctx, crClient, *credRef, namespace)
}

apiToken, err := getCredentialDataFromRef(ctx, crClient, *credRef, namespace)
if err == nil {
logger.Info("creating a verified linode client for create request", "name", resourceName)
linodeClient.SetToken(string(apiToken))
Expand All @@ -149,7 +152,7 @@ func setupClientWithCredentials(ctx context.Context, crClient clients.K8sClient,
// Handle error cases
if apierrors.IsNotFound(err) {
logger.Info("credentials secret not found, skipping API validation",
"name", resourceName, "secret", credRef.Name)
"name", resourceName, "secret", credName)
return true, linodeClient
}

Expand Down
Loading