in frontend/pkg/frontend/helpers.go [132:242]
func (f *Frontend) DeleteResource(ctx context.Context, resourceDoc *database.ResourceDocument) (string, *arm.CloudError) {
const operationRequest = database.OperationRequestDelete
var err error
logger := LoggerFromContext(ctx)
switch resourceDoc.InternalID.Kind() {
case arohcpv1alpha1.ClusterKind:
err = f.clusterServiceClient.DeleteCluster(ctx, resourceDoc.InternalID)
case arohcpv1alpha1.NodePoolKind:
err = f.clusterServiceClient.DeleteNodePool(ctx, resourceDoc.InternalID)
default:
logger.Error(fmt.Sprintf("unsupported Cluster Service path: %s", resourceDoc.InternalID))
return "", arm.NewInternalServerError()
}
if err != nil {
cloudError := CSErrorToCloudError(err, resourceDoc.ResourceID)
// Do not log attempts to delete a nonexistent
// resource because the end result is the same.
if cloudError.StatusCode != http.StatusNotFound {
logger.Error(err.Error())
}
return "", cloudError
}
// Cluster Service will take care of canceling any ongoing operations
// on the resource or child resources, but we need to do some database
// bookkeeping to reflect that.
// FIXME This would be a good place to use Cosmos DB's transactional batch
// operations to ensure all these write operations succeed together
// or roll back. We would need two parallel transactions: one for
// the Operations container and another for the Resources container.
// But we're stymied currently by the DBClient interface, and I have
// no desire to implement this in the in-memory cache. DBClient has
// served us well up to this point, but I think it's time to bid it
// farewell and switch to gomock in unit tests.
err = f.CancelActiveOperation(ctx, resourceDoc)
if err != nil {
logger.Error(err.Error())
return "", arm.NewInternalServerError()
}
operationDoc := database.NewOperationDocument(operationRequest, resourceDoc.ResourceID, resourceDoc.InternalID)
operationID, err := f.dbClient.CreateOperationDoc(ctx, operationDoc)
if err != nil {
logger.Error(err.Error())
return "", arm.NewInternalServerError()
}
_, err = f.dbClient.UpdateResourceDoc(ctx, resourceDoc.ResourceID, func(updateDoc *database.ResourceDocument) bool {
updateDoc.ActiveOperationID = operationID
updateDoc.ProvisioningState = operationDoc.Status
return true
})
if err != nil {
logger.Error(err.Error())
return "", arm.NewInternalServerError()
}
iterator := f.dbClient.ListResourceDocs(resourceDoc.ResourceID, -1, nil)
for _, child := range iterator.Items(ctx) {
// Anonymous function avoids repetitive error handling.
err = func() error {
err = f.CancelActiveOperation(ctx, child)
if err != nil {
return err
}
// This operation is not accessible through any REST endpoint.
// Its purpose is to cause the backend to delete the resource
// document once resource deletion completes.
childOperationDoc := database.NewOperationDocument(operationRequest, child.ResourceID, child.InternalID)
childOperationID, err := f.dbClient.CreateOperationDoc(ctx, childOperationDoc)
if err != nil {
return err
}
_, err = f.dbClient.UpdateResourceDoc(ctx, child.ResourceID, func(updateDoc *database.ResourceDocument) bool {
updateDoc.ActiveOperationID = childOperationID
updateDoc.ProvisioningState = childOperationDoc.Status
return true
})
if err != nil {
return err
}
return nil
}()
if err != nil {
logger.Error(err.Error())
return "", arm.NewInternalServerError()
}
}
err = iterator.GetError()
if err != nil {
logger.Error(err.Error())
return "", arm.NewInternalServerError()
}
return operationID, nil
}