Skip to content
Open
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
3 changes: 3 additions & 0 deletions cmd/ateapi/internal/controlapi/create_actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ func validateCreateActorRequest(req *ateapipb.CreateActorRequest) field.ErrorLis
if val := actor.GetWorkerSelector(); val != nil {
errs = append(errs, validateSelector(val, actorPath.Child("worker_selector"))...)
}
if val := actor.GetActorVolumes(); len(val) > 0 {
errs = append(errs, resources.ValidateExternalVolumes(val, actorPath.Child("actor_volumes"))...)
}
if val := req.GetSourceSnapshot(); val != nil {
if err := validateActorSnapshotRef(val, "source_snapshot"); err != nil {
errs = append(errs, field.Invalid(fldPath.Child("source_snapshot"), val, err.Error()))
Expand Down
81 changes: 81 additions & 0 deletions internal/resources/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,84 @@ func ValidateUUID(uuid string, fldPath *field.Path) field.ErrorList {
}
return nil
}

// ValidateExternalVolume checks that an external volume message is well-formed.
func ValidateExternalVolume(volume *ateapipb.ExternalVolume, fldPath *field.Path) field.ErrorList {
var errs field.ErrorList

if volume == nil {
return errs
}

if val, p := volume.VolumeName, fldPath.Child("volume_name"); val == "" {
errs = append(errs, field.Required(p, ""))
} else {
errs = append(errs, ValidateResourceName(val, p)...)
}

if val, p := volume.StorageVolumeId, fldPath.Child("storage_volume_id"); val != "" {
for _, r := range val {
if (r >= 0x0000 && r <= 0x0008) ||
r == 0x000B ||
r == 0x000C ||
(r >= 0x000E && r <= 0x001F) ||
(r >= 0x007F && r <= 0x009F) {
errs = append(errs, field.Invalid(p, val, "must not contain control characters (U+0000-U+0008, U+000B, U+000C, U+000E-U+001F, U+007F-U+009F)"))
break
}
}
}

if val, p := volume.VolumeType, fldPath.Child("volume_type"); val == "" {
errs = append(errs, field.Required(p, ""))
} else {
// CSI spec follows DNS subdomain restrictions with an additional total
// length restriction of 63 characters.
// https://github.com/container-storage-interface/spec
if len(val) > 63 {
errs = append(errs, field.TooLong(p, "" /* unused */, 63))
}
// Internal volume plugins may optionally have a "substrate.io/" prefix.
valToValidate := strings.TrimPrefix(val, "substrate.io/")
for _, msg := range content.IsDNS1123Subdomain(valToValidate) {
errs = append(errs, field.Invalid(p, val, msg))
}
}

// status is server-managed; accept any defined enum value (the unset/zero
// STATUS_UNSPECIFIED is tolerated for backward compatibility), reject unknowns.
if val, p := volume.Status, fldPath.Child("status"); ateapipb.ExternalVolume_Status_name[int32(val)] == "" {
errs = append(errs, field.NotSupported(p, val, []string{
ateapipb.ExternalVolume_STATUS_PENDING.String(),
ateapipb.ExternalVolume_STATUS_CREATED.String(),
ateapipb.ExternalVolume_STATUS_DELETING.String(),
}))
}

return errs
}

// ValidateExternalVolumes checks that a list of external volumes is well-formed
// and contains no duplicate volume names.
func ValidateExternalVolumes(volumes []*ateapipb.ExternalVolume, fldPath *field.Path) field.ErrorList {
var errs field.ErrorList
seenNames := make(map[string]bool)

for i, vol := range volumes {
idxPath := fldPath.Index(i)
if vol == nil {
errs = append(errs, field.Required(idxPath, ""))
continue
}
errs = append(errs, ValidateExternalVolume(vol, idxPath)...)
if vol.VolumeName != "" {
if seenNames[vol.VolumeName] {
errs = append(errs, field.Duplicate(idxPath.Child("volume_name"), vol.VolumeName))
} else {
seenNames[vol.VolumeName] = true
}
}
}

return errs
}
Loading
Loading