-
Notifications
You must be signed in to change notification settings - Fork 979
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for Subnets specification/override #454
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ import ( | |
"fmt" | ||
|
||
"github.com/awslabs/karpenter/pkg/utils/functional" | ||
"k8s.io/apimachinery/pkg/util/validation" | ||
"knative.dev/pkg/apis" | ||
) | ||
|
||
|
@@ -40,89 +41,118 @@ var ( | |
SupportedOperatingSystems = []string{} | ||
SupportedZones = []string{} | ||
SupportedInstanceTypes = []string{} | ||
ValidationHook func(ctx context.Context, spec *ProvisionerSpec) *apis.FieldError | ||
ConstraintsValidationHook func(ctx context.Context, constraints *Constraints) *apis.FieldError | ||
) | ||
|
||
func (p *Provisioner) Validate(ctx context.Context) (errs *apis.FieldError) { | ||
return errs.Also( | ||
apis.ValidateObjectMetadata(p), | ||
p.Spec.Validate(ctx), | ||
apis.ValidateObjectMetadata(p).ViaField("metadata"), | ||
p.Spec.validate(ctx).ViaField("spec"), | ||
njtran marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
} | ||
|
||
func (s *ProvisionerSpec) Validate(ctx context.Context) (errs *apis.FieldError) { | ||
errs = errs.Also( | ||
s.validateClusterSpec(ctx), | ||
s.validateLabels(ctx), | ||
s.validateZones(ctx), | ||
s.validateInstanceTypes(ctx), | ||
s.validateArchitecture(ctx), | ||
s.validateOperatingSystem(ctx), | ||
func (s *ProvisionerSpec) validate(ctx context.Context) (errs *apis.FieldError) { | ||
return errs.Also( | ||
s.Cluster.validate(ctx).ViaField("cluster"), | ||
// This validation is on the ProvisionerSpec despire the fact that | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: s/despire/despite/ |
||
// labels are a property of Constraints. This is necessary because | ||
// validation is applied to constraints that include pod overrides. | ||
// These labels are restricted when creating provisioners, but are not | ||
// restricted for pods since they're necessary to override constraints. | ||
s.validateRestrictedLabels(ctx), | ||
s.Constraints.Validate(ctx), | ||
) | ||
if ValidationHook != nil { | ||
errs = errs.Also(ValidationHook(ctx, s)) | ||
} | ||
|
||
func (s *ProvisionerSpec) validateRestrictedLabels(ctx context.Context) (errs *apis.FieldError) { | ||
for key := range s.Labels { | ||
if functional.ContainsString(RestrictedLabels, key) { | ||
errs = errs.Also(apis.ErrInvalidKeyName(key, "labels")) | ||
} | ||
} | ||
return errs | ||
} | ||
|
||
func (s *ProvisionerSpec) validateClusterSpec(ctx context.Context) (errs *apis.FieldError) { | ||
if s.Cluster == nil { | ||
return errs.Also(apis.ErrMissingField("spec.cluster")) | ||
func (s *ClusterSpec) validate(ctx context.Context) (errs *apis.FieldError) { | ||
if s == nil { | ||
return errs.Also(apis.ErrMissingField()) | ||
} | ||
if len(s.Name) == 0 { | ||
errs = errs.Also(apis.ErrMissingField("name")) | ||
} | ||
if len(s.Cluster.Name) == 0 { | ||
errs = errs.Also(apis.ErrMissingField("spec.cluster.name")) | ||
if len(s.Endpoint) == 0 { | ||
errs = errs.Also(apis.ErrMissingField("endpoint")) | ||
} | ||
if len(s.Cluster.Endpoint) == 0 { | ||
errs = errs.Also(apis.ErrMissingField("spec.cluster.endpoint")) | ||
if len(s.CABundle) == 0 { | ||
errs = errs.Also(apis.ErrMissingField("caBundle")) | ||
} | ||
if len(s.Cluster.CABundle) == 0 { | ||
errs = errs.Also(apis.ErrMissingField("spec.cluster.caBundle")) | ||
return errs | ||
} | ||
|
||
// Validate constraints subresource. This validation logic is used both upon | ||
// creation of a provisioner as well as when a pod is attempting to be | ||
// provisioned. If a provisioner fails validation, it will be rejected by the | ||
// API Server. If constraints.WithOverrides(pod) fails validation, the pod will | ||
// be ignored for provisioning. | ||
func (c *Constraints) Validate(ctx context.Context) (errs *apis.FieldError) { | ||
errs = errs.Also( | ||
c.validateLabels(ctx), | ||
c.validateArchitecture(ctx), | ||
c.validateOperatingSystem(ctx), | ||
c.validateZones(ctx), | ||
c.validateInstanceTypes(ctx), | ||
) | ||
if ConstraintsValidationHook != nil { | ||
errs = errs.Also(ConstraintsValidationHook(ctx, c)) | ||
} | ||
return errs | ||
} | ||
|
||
func (s *ProvisionerSpec) validateLabels(ctx context.Context) (errs *apis.FieldError) { | ||
for _, restricted := range RestrictedLabels { | ||
if _, ok := s.Labels[restricted]; ok { | ||
errs = errs.Also(apis.ErrInvalidKeyName(restricted, "spec.labels")) | ||
func (c *Constraints) validateLabels(ctx context.Context) (errs *apis.FieldError) { | ||
for key, value := range c.Labels { | ||
for _, err := range validation.IsQualifiedName(key) { | ||
errs = errs.Also(apis.ErrInvalidKeyName(key, "labels", err)) | ||
} | ||
for _, err := range validation.IsValidLabelValue(value) { | ||
errs = errs.Also(apis.ErrInvalidValue(value+", "+err, "labels")) | ||
} | ||
} | ||
return errs | ||
} | ||
|
||
func (s *ProvisionerSpec) validateArchitecture(ctx context.Context) (errs *apis.FieldError) { | ||
if s.Architecture == nil { | ||
func (c *Constraints) validateArchitecture(ctx context.Context) (errs *apis.FieldError) { | ||
if c.Architecture == nil { | ||
return nil | ||
} | ||
if !functional.ContainsString(SupportedArchitectures, *s.Architecture) { | ||
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s not in %v", *s.Architecture, SupportedArchitectures), "spec.architecture")) | ||
if !functional.ContainsString(SupportedArchitectures, *c.Architecture) { | ||
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s not in %v", *c.Architecture, SupportedArchitectures), "architecture")) | ||
} | ||
return errs | ||
} | ||
|
||
func (s *ProvisionerSpec) validateOperatingSystem(ctx context.Context) (errs *apis.FieldError) { | ||
if s.OperatingSystem == nil { | ||
func (c *Constraints) validateOperatingSystem(ctx context.Context) (errs *apis.FieldError) { | ||
if c.OperatingSystem == nil { | ||
return nil | ||
} | ||
if !functional.ContainsString(SupportedOperatingSystems, *s.OperatingSystem) { | ||
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s not in %v", *s.OperatingSystem, SupportedOperatingSystems), "spec.operatingSystem")) | ||
if !functional.ContainsString(SupportedOperatingSystems, *c.OperatingSystem) { | ||
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s not in %v", *c.OperatingSystem, SupportedOperatingSystems), "operatingSystem")) | ||
} | ||
return errs | ||
} | ||
|
||
func (s *ProvisionerSpec) validateZones(ctx context.Context) (errs *apis.FieldError) { | ||
for i, zone := range s.Zones { | ||
func (c *Constraints) validateZones(ctx context.Context) (errs *apis.FieldError) { | ||
for i, zone := range c.Zones { | ||
if !functional.ContainsString(SupportedZones, zone) { | ||
errs = errs.Also(apis.ErrInvalidArrayValue(fmt.Sprintf("%s not in %v", zone, SupportedZones), "spec.zones", i)) | ||
errs = errs.Also(apis.ErrInvalidArrayValue(fmt.Sprintf("%s not in %v", zone, SupportedZones), "zones", i)) | ||
} | ||
} | ||
return errs | ||
} | ||
|
||
func (s *ProvisionerSpec) validateInstanceTypes(ctx context.Context) (errs *apis.FieldError) { | ||
for i, instanceType := range s.InstanceTypes { | ||
func (c *Constraints) validateInstanceTypes(ctx context.Context) (errs *apis.FieldError) { | ||
for i, instanceType := range c.InstanceTypes { | ||
if !functional.ContainsString(SupportedInstanceTypes, instanceType) { | ||
errs = errs.Also(apis.ErrInvalidArrayValue(fmt.Sprintf("%s not in %v", instanceType, SupportedInstanceTypes), "spec.instanceTypes", i)) | ||
errs = errs.Also(apis.ErrInvalidArrayValue(fmt.Sprintf("%s not in %v", instanceType, SupportedInstanceTypes), "instanceTypes", i)) | ||
} | ||
} | ||
return errs | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,22 +65,31 @@ var _ = Describe("Validation", func() { | |
} | ||
}) | ||
|
||
It("should fail for restricted labels", func() { | ||
for _, label := range []string{ | ||
ArchitectureLabelKey, | ||
OperatingSystemLabelKey, | ||
ProvisionerNameLabelKey, | ||
ProvisionerNamespaceLabelKey, | ||
ProvisionerPhaseLabel, | ||
ProvisionerTTLKey, | ||
ZoneLabelKey, | ||
InstanceTypeLabelKey, | ||
} { | ||
provisioner.Spec.Labels = map[string]string{label: randomdata.SillyName()} | ||
Context("Labels", func() { | ||
It("should fail for invalid label keys", func() { | ||
provisioner.Spec.Labels = map[string]string{"spaces are not allowed": randomdata.SillyName()} | ||
Expect(provisioner.Validate(ctx)).ToNot(Succeed()) | ||
} | ||
}) | ||
It("should fail for invalid label values", func() { | ||
provisioner.Spec.Labels = map[string]string{randomdata.SillyName(): "/ is not allowed"} | ||
Expect(provisioner.Validate(ctx)).ToNot(Succeed()) | ||
}) | ||
It("should fail for restricted labels", func() { | ||
for _, label := range []string{ | ||
ArchitectureLabelKey, | ||
OperatingSystemLabelKey, | ||
ProvisionerNameLabelKey, | ||
ProvisionerNamespaceLabelKey, | ||
ProvisionerPhaseLabel, | ||
ProvisionerTTLKey, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an annotation, so you may not need to check this in labels. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! |
||
ZoneLabelKey, | ||
InstanceTypeLabelKey, | ||
} { | ||
provisioner.Spec.Labels = map[string]string{label: randomdata.SillyName()} | ||
Expect(provisioner.Validate(ctx)).ToNot(Succeed()) | ||
} | ||
}) | ||
}) | ||
|
||
Context("Zones", func() { | ||
SupportedZones = append(SupportedZones, "test-zone-1") | ||
It("should succeed if unspecified", func() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this also be done with
c.Labels[key] = value
?functional.UnionStringMaps()
works with "last write wins", so it seems like we overwrite an existing entry inc.labels
with either versions, so it might be cleaner just to add it in.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oopsie, nice call!