func()

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())
	}
}