99 "github.com/openshift-hyperfleet/hyperfleet-api/pkg/api"
1010 "github.com/openshift-hyperfleet/hyperfleet-api/pkg/auth"
1111 "github.com/openshift-hyperfleet/hyperfleet-api/pkg/dao"
12+ "github.com/openshift-hyperfleet/hyperfleet-api/pkg/db"
1213 "github.com/openshift-hyperfleet/hyperfleet-api/pkg/errors"
1314 "github.com/openshift-hyperfleet/hyperfleet-api/pkg/registry"
1415)
@@ -128,7 +129,9 @@ func (s *sqlResourceService) Patch(
128129
129130// Delete performs a soft-delete by setting DeletedTime and DeletedBy, then incrementing generation.
130131// Idempotent — returns the resource unchanged if already marked for deletion.
131- // Child cascade policies are not enforced here (deferred to HYPERFLEET-1093).
132+ // Before deleting, enforces child delete policies from the entity registry:
133+ // - restrict: blocks deletion (409) if active children of that type exist
134+ // - cascade: recursively soft-deletes children (DFS, innermost first)
132135func (s * sqlResourceService ) Delete (ctx context.Context , kind , id string ) (* api.Resource , * errors.ServiceError ) {
133136 if svcErr := validateKind (kind ); svcErr != nil {
134137 return nil , svcErr
@@ -138,7 +141,6 @@ func (s *sqlResourceService) Delete(ctx context.Context, kind, id string) (*api.
138141 return nil , handleSoftDeleteError (kind , err )
139142 }
140143
141- // Already marked for deletion — return as-is to keep the operation idempotent.
142144 if resource .DeletedTime != nil {
143145 return resource , nil
144146 }
@@ -151,13 +153,75 @@ func (s *sqlResourceService) Delete(ctx context.Context, kind, id string) (*api.
151153 resource .MarkDeleted (username , deletedTime )
152154 resource .IncrementGeneration ()
153155
156+ if svcErr := s .enforceDeletePolicies (ctx , resource ); svcErr != nil {
157+ db .MarkForRollback (ctx , svcErr )
158+ return nil , svcErr
159+ }
160+
154161 if saveErr := s .resourceDao .Save (ctx , resource ); saveErr != nil {
155162 return nil , handleSoftDeleteError (kind , saveErr )
156163 }
157164
158165 return resource , nil
159166}
160167
168+ func (s * sqlResourceService ) enforceDeletePolicies (
169+ ctx context.Context , resource * api.Resource ,
170+ ) * errors.ServiceError {
171+ children := registry .ChildrenOf (resource .Kind )
172+
173+ for _ , child := range children {
174+ if child .OnParentDelete == registry .OnParentDeleteRestrict {
175+ if svcErr := s .checkCanDelete (ctx , resource , child ); svcErr != nil {
176+ return svcErr
177+ }
178+ }
179+ }
180+
181+ for _ , child := range children {
182+ if child .OnParentDelete == registry .OnParentDeleteCascade {
183+ items , err := s .resourceDao .FindByKindAndOwner (ctx , child .Kind , resource .ID )
184+ if err != nil {
185+ return errors .GeneralError (
186+ "Unable to find %s children for cascade delete: %s" , child .Kind , err ,
187+ )
188+ }
189+ for _ , item := range items {
190+ if item .DeletedTime != nil {
191+ continue
192+ }
193+ item .MarkDeleted (* resource .DeletedBy , * resource .DeletedTime )
194+ item .IncrementGeneration ()
195+
196+ if svcErr := s .enforceDeletePolicies (ctx , item ); svcErr != nil {
197+ return svcErr
198+ }
199+ if saveErr := s .resourceDao .Save (ctx , item ); saveErr != nil {
200+ return handleSoftDeleteError (child .Kind , saveErr )
201+ }
202+ }
203+ }
204+ }
205+
206+ return nil
207+ }
208+
209+ func (s * sqlResourceService ) checkCanDelete (
210+ ctx context.Context , resource * api.Resource , child registry.EntityDescriptor ,
211+ ) * errors.ServiceError {
212+ exists , err := s .resourceDao .ExistsByOwner (ctx , child .Kind , resource .ID )
213+ if err != nil {
214+ return errors .GeneralError ("Unable to check %s children: %s" , child .Kind , err )
215+ }
216+ if exists {
217+ return errors .ConflictState (
218+ "Cannot delete %s '%s': active %s(s) exist" ,
219+ resource .Kind , resource .ID , child .Kind ,
220+ )
221+ }
222+ return nil
223+ }
224+
161225// FindByIDs returns resources matching the given IDs, scoped to the specified kind.
162226func (s * sqlResourceService ) FindByIDs (
163227 ctx context.Context , kind string , ids []string ,
0 commit comments