in frontend/pkg/frontend/frontend.go [179:336]
func (f *Frontend) ArmResourceList(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
}
var pageSizeHint int32 = 20
var continuationToken *string
// The Resource Provider Contract implies $top is only honored when
// following a "nextLink" after the initial collection GET request.
// So only check for it when the URL includes a $skipToken.
urlQuery := request.URL.Query()
if urlQuery.Has("$skipToken") {
continuationToken = api.Ptr(urlQuery.Get("$skipToken"))
top, err := strconv.ParseInt(urlQuery.Get("$top"), 10, 32)
if err == nil && top > 0 {
pageSizeHint = int32(top)
}
}
// FIXME We may want to cap pageSizeHint. If we get a large enough
// $top argument (and there's enough actual clusters to reach
// that), we could potentially hit the 8MB response size limit.
subscriptionID := request.PathValue(PathSegmentSubscriptionID)
resourceGroupName := request.PathValue(PathSegmentResourceGroupName)
resourceName := request.PathValue(PathSegmentResourceName)
resourceTypeName := path.Base(request.URL.Path)
// Even though the bulk of the list content comes from Cluster Service,
// we start by querying Cosmos DB because its continuation token meets
// the requirements of a skipToken for ARM pagination. We then query
// Cluster Service for the exact set of IDs returned by Cosmos.
prefixString := "/subscriptions/" + subscriptionID
if resourceGroupName != "" {
prefixString += "/resourceGroups/" + resourceGroupName
}
if resourceName != "" {
// This is a nested resource request. Build a resource ID for
// the parent cluster. We use this below to get the cluster's
// ResourceDocument from Cosmos DB.
prefixString += "/providers/" + api.ProviderNamespace
prefixString += "/" + api.ClusterResourceTypeName + "/" + resourceName
}
prefix, err := azcorearm.ParseResourceID(prefixString)
if err != nil {
logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}
dbIterator := f.dbClient.ListResourceDocs(prefix, pageSizeHint, continuationToken)
// Build a map of cluster documents by Cluster Service cluster ID.
documentMap := make(map[string]*database.ResourceDocument)
for _, doc := range dbIterator.Items(ctx) {
// FIXME This filtering could be made part of the query expression. It would
// require some reworking (or elimination) of the DBClient interface.
if strings.HasSuffix(strings.ToLower(doc.ResourceID.ResourceType.Type), resourceTypeName) {
documentMap[doc.InternalID.ID()] = doc
}
}
err = dbIterator.GetError()
if err != nil {
logger.Error(err.Error())
arm.WriteInternalServerError(writer)
}
// Build a Cluster Service query that looks for
// the specific IDs returned by the Cosmos query.
queryIDs := make([]string, 0, len(documentMap))
for key := range documentMap {
queryIDs = append(queryIDs, "'"+key+"'")
}
query := fmt.Sprintf("id in (%s)", strings.Join(queryIDs, ", "))
logger.Info(fmt.Sprintf("Searching Cluster Service for %q", query))
pagedResponse := arm.NewPagedResponse()
switch resourceTypeName {
case strings.ToLower(api.ClusterResourceTypeName):
csIterator := f.clusterServiceClient.ListClusters(query)
for csCluster := range csIterator.Items(ctx) {
if doc, ok := documentMap[csCluster.ID()]; ok {
value, err := marshalCSCluster(csCluster, doc, versionedInterface)
if err != nil {
logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}
pagedResponse.AddValue(value)
}
}
err = csIterator.GetError()
case strings.ToLower(api.NodePoolResourceTypeName):
var resourceDoc *database.ResourceDocument
// Fetch the cluster document for the Cluster Service ID.
resourceDoc, err = f.dbClient.GetResourceDoc(ctx, prefix)
if err != nil {
logger.Error(err.Error())
if errors.Is(err, database.ErrNotFound) {
arm.WriteResourceNotFoundError(writer, prefix)
} else {
arm.WriteInternalServerError(writer)
}
return
}
csIterator := f.clusterServiceClient.ListNodePools(resourceDoc.InternalID, query)
for csNodePool := range csIterator.Items(ctx) {
if doc, ok := documentMap[csNodePool.ID()]; ok {
value, err := marshalCSNodePool(csNodePool, doc, versionedInterface)
if err != nil {
logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}
pagedResponse.AddValue(value)
}
}
err = csIterator.GetError()
default:
err = fmt.Errorf("unsupported resource type: %s", resourceTypeName)
}
// Check for iteration error.
if err != nil {
logger.Error(err.Error())
arm.WriteCloudError(writer, CSErrorToCloudError(err, nil))
return
}
// MiddlewareReferer ensures Referer is present.
err = pagedResponse.SetNextLink(request.Referer(), dbIterator.GetContinuationToken())
if err != nil {
logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}
_, err = arm.WriteJSONResponse(writer, http.StatusOK, pagedResponse)
if err != nil {
logger.Error(err.Error())
}
}