func()

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
}