in frontend/pkg/frontend/frontend.go [1225:1350]
func (f *Frontend) OperationResult(writer http.ResponseWriter, request *http.Request) {
ctx := request.Context()
logger := LoggerFromContext(ctx)
versionedInterface, err := VersionFromContext(ctx)
if err != nil {
logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}
resourceID, err := ResourceIDFromContext(ctx)
if err != nil {
logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}
pk := database.NewPartitionKey(resourceID.SubscriptionID)
doc, err := f.dbClient.GetOperationDoc(ctx, pk, resourceID.Name)
if err != nil {
logger.Error(err.Error())
if errors.Is(err, database.ErrNotFound) {
writer.WriteHeader(http.StatusNotFound)
} else {
writer.WriteHeader(http.StatusInternalServerError)
}
return
}
// Validate the identity retrieving the operation result is the
// same identity that triggered the operation. Return 404 if not.
if !f.OperationIsVisible(request, resourceID.Name, doc) {
writer.WriteHeader(http.StatusNotFound)
return
}
// Handle non-terminal statuses and (maybe?) failure/cancellation.
//
// XXX ARM requirements for failed async operations get fuzzy here.
//
// My best understanding, based on a Stack Overflow answer [1], is
// returning an Azure-AsyncOperation header will cause ARM to poll
// that endpoint first.
//
// If ARM finds the operation in a "Failed" or "Canceled" state,
// it will propagate details from the response's "error" property.
//
// If ARM finds the operation in a "Succeeded" state, ONLY THEN is
// this endpoint called (if a Location header was also returned).
//
// So for the "Failed or Canceled" case we just give a generic
// "Internal Server Error" response since, in theory, this case
// should never be reached.
//
// [1] https://stackoverflow.microsoft.com/a/318573/106707
//
switch doc.Status {
case arm.ProvisioningStateSucceeded:
// Handled below.
case arm.ProvisioningStateFailed, arm.ProvisioningStateCanceled:
// Should never be reached?
arm.WriteInternalServerError(writer)
return
default:
// Operation is still in progress.
f.AddLocationHeader(writer, request, doc)
writer.WriteHeader(http.StatusAccepted)
return
}
// The response henceforth should be exactly as though the operation
// succeeded synchronously.
var successStatusCode int
switch doc.Request {
case database.OperationRequestCreate:
successStatusCode = http.StatusCreated
case database.OperationRequestUpdate:
successStatusCode = http.StatusOK
case database.OperationRequestDelete:
writer.WriteHeader(http.StatusNoContent)
return
case database.OperationRequestRequestCredential:
successStatusCode = http.StatusOK
case database.OperationRequestRevokeCredentials:
writer.WriteHeader(http.StatusNoContent)
return
default:
logger.Error(fmt.Sprintf("Unhandled request type: %s", doc.Request))
writer.WriteHeader(http.StatusInternalServerError)
return
}
var responseBody []byte
if doc.InternalID.Kind() == cmv1.BreakGlassCredentialKind {
csBreakGlassCredential, err := f.clusterServiceClient.GetBreakGlassCredential(ctx, doc.InternalID)
if err != nil {
logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}
responseBody, err = versionedInterface.MarshalHCPOpenShiftClusterAdminCredential(ConvertCStoAdminCredential(csBreakGlassCredential))
if err != nil {
logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}
} else {
var cloudError *arm.CloudError
responseBody, cloudError = f.MarshalResource(ctx, doc.ExternalID, versionedInterface)
if cloudError != nil {
arm.WriteCloudError(writer, cloudError)
return
}
}
_, err = arm.WriteJSONResponse(writer, successStatusCode, responseBody)
if err != nil {
logger.Error(err.Error())
}
}