@@ -379,15 +379,45 @@ func (s *server) createSpritz(c echo.Context) error {
379379 }
380380 body .Spec .Owner = owner
381381
382+ nameProvided := body .Name != ""
383+ var nameGenerator func () string
384+ namePrefix := resolveSpritzNamePrefix (body .NamePrefix , body .Spec .Image )
385+ if namePrefix == "" && preset != nil {
386+ namePrefix = resolveSpritzNamePrefix (preset .NamePrefix , preset .Image )
387+ }
388+ if ! nameProvided {
389+ generator , err := s .newSpritzNameGenerator (c .Request ().Context (), namespace , namePrefix )
390+ if err != nil {
391+ return writeError (c , http .StatusInternalServerError , "failed to generate spritz name" )
392+ }
393+ nameGenerator = generator
394+ body .Name = nameGenerator ()
395+ }
396+ if body .Name == "" {
397+ return writeError (c , http .StatusInternalServerError , "failed to generate spritz name" )
398+ }
399+
382400 if principal .isService () {
383- fingerprint , err := s .validateProvisionerCreate (c .Request ().Context (), principal , namespace , & body , normalizedUserConfig , requestedImage , requestedRepo , requestedNamespace )
401+ fingerprintName := body .Name
402+ if ! nameProvided {
403+ fingerprintName = ""
404+ }
405+ fingerprint , err := s .validateProvisionerCreate (c .Request ().Context (), principal , namespace , & body , normalizedUserConfig , requestedImage , requestedRepo , requestedNamespace , fingerprintName )
384406 if err != nil {
385407 if errors .Is (err , errForbidden ) {
386408 return writeError (c , http .StatusForbidden , "forbidden" )
387409 }
388410 return writeError (c , http .StatusBadRequest , err .Error ())
389411 }
390- existing , err := findIdempotentSpritz (c .Request ().Context (), s .client , namespace , principal .ID , body .IdempotencyKey )
412+ reservedName , completed , err := s .reserveIdempotentCreateName (c .Request ().Context (), namespace , principal , body .IdempotencyKey , fingerprint , body .Name )
413+ if err != nil {
414+ if strings .Contains (err .Error (), "idempotencyKey already used" ) {
415+ return writeError (c , http .StatusConflict , err .Error ())
416+ }
417+ return writeError (c , http .StatusInternalServerError , err .Error ())
418+ }
419+ body .Name = reservedName
420+ existing , err := s .findReservedSpritz (c .Request ().Context (), namespace , reservedName )
391421 if err != nil {
392422 return writeError (c , http .StatusInternalServerError , err .Error ())
393423 }
@@ -397,12 +427,18 @@ func (s *server) createSpritz(c echo.Context) error {
397427 }
398428 return writeJSON (c , http .StatusOK , summarizeCreateResponse (existing , principal , body .PresetID , provisionerSource (& body ), body .IdempotencyKey , true ))
399429 }
430+ if completed {
431+ return writeError (c , http .StatusConflict , "idempotencyKey already used" )
432+ }
433+ if err := s .enforceProvisionerQuotas (c .Request ().Context (), namespace , principal , body .Spec .Owner .ID ); err != nil {
434+ return writeError (c , http .StatusBadRequest , err .Error ())
435+ }
400436 body .Annotations = mergeStringMap (body .Annotations , map [string ]string {
401- actorIDAnnotationKey : principal .ID ,
402- actorTypeAnnotationKey : string (principal .Type ),
403- sourceAnnotationKey : provisionerSource (& body ),
404- requestIDAnnotationKey : body .RequestID ,
405- idempotencyKeyAnnotationKey : body .IdempotencyKey ,
437+ actorIDAnnotationKey : principal .ID ,
438+ actorTypeAnnotationKey : string (principal .Type ),
439+ sourceAnnotationKey : provisionerSource (& body ),
440+ requestIDAnnotationKey : body .RequestID ,
441+ idempotencyKeyAnnotationKey : body .IdempotencyKey ,
406442 idempotencyHashAnnotationKey : fingerprint ,
407443 })
408444 } else if s .auth .enabled () && ! principal .isAdminPrincipal () && owner .ID != principal .ID {
@@ -413,24 +449,6 @@ func (s *server) createSpritz(c echo.Context) error {
413449 return writeError (c , http .StatusBadRequest , err .Error ())
414450 }
415451
416- nameProvided := body .Name != ""
417- var nameGenerator func () string
418- if ! nameProvided {
419- namePrefix := resolveSpritzNamePrefix (body .NamePrefix , body .Spec .Image )
420- if namePrefix == "" && preset != nil {
421- namePrefix = resolveSpritzNamePrefix (preset .NamePrefix , preset .Image )
422- }
423- generator , err := s .newSpritzNameGenerator (c .Request ().Context (), namespace , namePrefix )
424- if err != nil {
425- return writeError (c , http .StatusInternalServerError , "failed to generate spritz name" )
426- }
427- nameGenerator = generator
428- body .Name = nameGenerator ()
429- }
430- if body .Name == "" {
431- return writeError (c , http .StatusInternalServerError , "failed to generate spritz name" )
432- }
433-
434452 labels := map [string ]string {
435453 ownerLabelKey : ownerLabelValue (owner .ID ),
436454 }
@@ -508,11 +526,26 @@ func (s *server) createSpritz(c echo.Context) error {
508526 return writeError (c , http .StatusBadRequest , err .Error ())
509527 }
510528 if err := s .client .Create (c .Request ().Context (), spritz ); err != nil {
529+ if principal .isService () && apierrors .IsAlreadyExists (err ) {
530+ existing , getErr := s .findReservedSpritz (c .Request ().Context (), namespace , name )
531+ if getErr != nil {
532+ return writeError (c , http .StatusInternalServerError , getErr .Error ())
533+ }
534+ if existing != nil && strings .TrimSpace (existing .Annotations [idempotencyHashAnnotationKey ]) == strings .TrimSpace (annotations [idempotencyHashAnnotationKey ]) {
535+ return writeJSON (c , http .StatusOK , summarizeCreateResponse (existing , principal , body .PresetID , provisionerSource (& body ), body .IdempotencyKey , true ))
536+ }
537+ return writeError (c , http .StatusConflict , "idempotencyKey already used with a different request" )
538+ }
511539 if ! nameProvided && apierrors .IsAlreadyExists (err ) {
512540 continue
513541 }
514542 return writeError (c , http .StatusInternalServerError , err .Error ())
515543 }
544+ if principal .isService () {
545+ if err := s .completeIdempotencyReservation (c .Request ().Context (), namespace , principal .ID , body .IdempotencyKey , spritz ); err != nil {
546+ return writeError (c , http .StatusInternalServerError , err .Error ())
547+ }
548+ }
516549 return writeJSON (c , http .StatusCreated , summarizeCreateResponse (spritz , principal , body .PresetID , provisionerSource (& body ), body .IdempotencyKey , false ))
517550 }
518551
0 commit comments