Skip to content

Commit 10ed16e

Browse files
author
Matthew Barnes
committed
frontend: Generalize ArmResourceList to also handle node pools
I was surprised to discover the collection endpoint for node pools is a valid resource ID (according to ParseResourceID), whereas the collection endpoints for clusters (by subscription and by resource group) are not. This required tweaking the static validation middleware, which was blocking node pool collection requests because the parsed resource ID had no resource name.
1 parent 53de71c commit 10ed16e

File tree

4 files changed

+94
-48
lines changed

4 files changed

+94
-48
lines changed

frontend/pkg/frontend/frontend.go

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"net"
1414
"net/http"
1515
"os"
16+
"path"
1617
"strconv"
1718
"strings"
1819
"sync/atomic"
@@ -175,6 +176,8 @@ func (f *Frontend) ArmResourceList(writer http.ResponseWriter, request *http.Req
175176

176177
subscriptionID := request.PathValue(PathSegmentSubscriptionID)
177178
resourceGroupName := request.PathValue(PathSegmentResourceGroupName)
179+
resourceName := request.PathValue(PathSegmentResourceName)
180+
resourceTypeName := path.Base(request.URL.Path)
178181

179182
// Even though the bulk of the list content comes from Cluster Service,
180183
// we start by querying Cosmos DB because its continuation token meets
@@ -185,18 +188,22 @@ func (f *Frontend) ArmResourceList(writer http.ResponseWriter, request *http.Req
185188
if resourceGroupName != "" {
186189
prefixString += "/resourceGroups/" + resourceGroupName
187190
}
191+
if resourceName != "" {
192+
prefixString += "/providers/" + api.ProviderNamespace
193+
prefixString += "/" + api.ClusterResourceTypeName + "/" + resourceName
194+
}
188195
prefix, err := arm.ParseResourceID(prefixString)
189196
if err != nil {
190197
f.logger.Error(err.Error())
191198
arm.WriteInternalServerError(writer)
192199
return
193200
}
194201

195-
iterator := f.dbClient.ListResourceDocs(ctx, prefix, pageSizeHint, continuationToken)
202+
dbIterator := f.dbClient.ListResourceDocs(ctx, prefix, pageSizeHint, continuationToken)
196203

197204
// Build a map of cluster documents by Cluster Service cluster ID.
198205
documentMap := make(map[string]*database.ResourceDocument)
199-
for item := range iterator.Items(ctx) {
206+
for item := range dbIterator.Items(ctx) {
200207
var doc database.ResourceDocument
201208

202209
err = json.Unmarshal(item, &doc)
@@ -206,12 +213,12 @@ func (f *Frontend) ArmResourceList(writer http.ResponseWriter, request *http.Req
206213
return
207214
}
208215

209-
if strings.EqualFold(doc.Key.ResourceType.String(), api.ClusterResourceType.String()) {
216+
if strings.HasSuffix(strings.ToLower(doc.Key.ResourceType.Type), resourceTypeName) {
210217
documentMap[doc.InternalID.ID()] = &doc
211218
}
212219
}
213220

214-
err = iterator.GetError()
221+
err = dbIterator.GetError()
215222
if err != nil {
216223
f.logger.Error(err.Error())
217224
arm.WriteInternalServerError(writer)
@@ -226,33 +233,61 @@ func (f *Frontend) ArmResourceList(writer http.ResponseWriter, request *http.Req
226233
query := fmt.Sprintf("id in (%s)", strings.Join(queryIDs, ", "))
227234
f.logger.Info(fmt.Sprintf("Searching Cluster Service for %q", query))
228235

229-
listRequest := f.clusterServiceClient.GetConn().ClustersMgmt().V1().Clusters().List().Search(query)
236+
switch resourceTypeName {
237+
case strings.ToLower(api.ClusterResourceTypeName):
238+
csIterator := f.clusterServiceClient.ListCSClusters(query)
239+
240+
for csCluster := range csIterator.Items(ctx) {
241+
if doc, ok := documentMap[csCluster.ID()]; ok {
242+
value, err := marshalCSCluster(csCluster, doc, versionedInterface)
243+
if err != nil {
244+
f.logger.Error(err.Error())
245+
arm.WriteInternalServerError(writer)
246+
return
247+
}
248+
pagedResponse.AddValue(value)
249+
}
250+
}
251+
err = csIterator.GetError()
252+
253+
case strings.ToLower(api.NodePoolResourceTypeName):
254+
var resourceDoc *database.ResourceDocument
255+
256+
// Fetch the cluster document for the Cluster Service ID.
257+
resourceDoc, err = f.dbClient.GetResourceDoc(ctx, prefix)
258+
if err != nil {
259+
f.logger.Error(err.Error())
260+
arm.WriteInternalServerError(writer)
261+
return
262+
}
263+
264+
csIterator := f.clusterServiceClient.ListCSNodePools(resourceDoc.InternalID, query)
230265

231-
// XXX This SHOULD avoid dealing with pagination from Cluster Service.
232-
// As far I can tell, uhc-cluster-service does not impose its own
233-
// limit on the page size. Further testing is needed to verify.
234-
listRequest.Size(len(documentMap))
266+
for csNodePool := range csIterator.Items(ctx) {
267+
if doc, ok := documentMap[csNodePool.ID()]; ok {
268+
value, err := marshalCSNodePool(csNodePool, doc, versionedInterface)
269+
if err != nil {
270+
f.logger.Error(err.Error())
271+
arm.WriteInternalServerError(writer)
272+
return
273+
}
274+
pagedResponse.AddValue(value)
275+
}
276+
}
277+
err = csIterator.GetError()
235278

236-
listResponse, err := listRequest.SendContext(ctx)
279+
default:
280+
err = fmt.Errorf("unsupported resource type: %s", resourceTypeName)
281+
}
282+
283+
// Check for iteration error.
237284
if err != nil {
238285
f.logger.Error(err.Error())
239286
arm.WriteInternalServerError(writer)
240287
return
241288
}
242289

243-
for _, csCluster := range listResponse.Items().Slice() {
244-
if doc, ok := documentMap[csCluster.ID()]; ok {
245-
value, err := marshalCSCluster(csCluster, doc, versionedInterface)
246-
if err != nil {
247-
f.logger.Error(err.Error())
248-
arm.WriteInternalServerError(writer)
249-
return
250-
}
251-
pagedResponse.AddValue(value)
252-
}
253-
}
254-
255-
err = pagedResponse.SetNextLink(request.Referer(), iterator.GetContinuationToken())
290+
err = pagedResponse.SetNextLink(request.Referer(), dbIterator.GetContinuationToken())
256291
if err != nil {
257292
f.logger.Error(err.Error())
258293
arm.WriteInternalServerError(writer)

frontend/pkg/frontend/middleware_resourceid_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ func TestMiddlewareResourceID(t *testing.T) {
6161
"Microsoft.Resources/tenants",
6262
},
6363
},
64+
{
65+
name: "node pool collection",
66+
path: "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/MyResourceGroup/PROVIDERS/MICROSOFT.REDHATOPENSHIFT/HCPOPENSHIFTCLUSTERS/myCluster/NODEPOOLS",
67+
resourceTypes: []string{
68+
"MICROSOFT.REDHATOPENSHIFT/HCPOPENSHIFTCLUSTERS/NODEPOOLS",
69+
"MICROSOFT.REDHATOPENSHIFT/HCPOPENSHIFTCLUSTERS",
70+
"Microsoft.Resources/resourceGroups",
71+
"Microsoft.Resources/subscriptions",
72+
"Microsoft.Resources/tenants",
73+
},
74+
},
6475
{
6576
name: "preflight deployment",
6677
path: "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/MyResourceGroup/PROVIDERS/MICROSOFT.REDHATOPENSHIFT/DEPLOYMENTS/preflight",

frontend/pkg/frontend/middleware_validatestatic.go

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ import (
1818
var rxHCPOpenShiftClusterResourceName = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9-]{2,53}$`)
1919
var rxNodePoolResourceName = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9-]{2,14}$`)
2020

21-
var resourceTypeSubscription = "Microsoft.Resources/subscriptions"
22-
2321
func MiddlewareValidateStatic(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
2422
// To conform with "OAPI012: Resource IDs must not be case sensitive"
2523
// we need to use the original, non-lowercased resource ID components
@@ -40,29 +38,28 @@ func MiddlewareValidateStatic(w http.ResponseWriter, r *http.Request, next http.
4038
}
4139
}
4240

43-
// Skip static validation for subscription resources
44-
if !strings.EqualFold(resource.ResourceType.String(), resourceTypeSubscription) {
45-
switch strings.ToLower(resource.ResourceType.Type) {
46-
case strings.ToLower(api.ClusterResourceType.Type):
47-
if !rxHCPOpenShiftClusterResourceName.MatchString(resource.Name) {
48-
arm.WriteError(w, http.StatusBadRequest,
49-
arm.CloudErrorCodeInvalidResourceName,
50-
resource.String(),
51-
"The Resource '%s/%s' under resource group '%s' does not conform to the naming restriction.",
52-
resource.ResourceType, resource.Name,
53-
resource.ResourceGroupName)
54-
return
55-
}
56-
case strings.ToLower(api.NodePoolResourceType.Type):
57-
if !rxNodePoolResourceName.MatchString(resource.Name) {
58-
arm.WriteError(w, http.StatusBadRequest,
59-
arm.CloudErrorCodeInvalidResourceName,
60-
resource.String(),
61-
"The Resource '%s/%s' under resource group '%s' does not conform to the naming restriction.",
62-
resource.ResourceType, resource.Name,
63-
resource.ResourceGroupName)
64-
return
65-
}
41+
switch strings.ToLower(resource.ResourceType.Type) {
42+
case strings.ToLower(api.ClusterResourceType.Type):
43+
if !rxHCPOpenShiftClusterResourceName.MatchString(resource.Name) {
44+
arm.WriteError(w, http.StatusBadRequest,
45+
arm.CloudErrorCodeInvalidResourceName,
46+
resource.String(),
47+
"The Resource '%s/%s' under resource group '%s' does not conform to the naming restriction.",
48+
resource.ResourceType, resource.Name,
49+
resource.ResourceGroupName)
50+
return
51+
}
52+
case strings.ToLower(api.NodePoolResourceType.Type):
53+
// The collection GET endpoint for nested resources
54+
// parses into a ResourceID with an empty Name field.
55+
if resource.Name != "" && !rxNodePoolResourceName.MatchString(resource.Name) {
56+
arm.WriteError(w, http.StatusBadRequest,
57+
arm.CloudErrorCodeInvalidResourceName,
58+
resource.String(),
59+
"The Resource '%s/%s' under resource group '%s' does not conform to the naming restriction.",
60+
resource.ResourceType, resource.Name,
61+
resource.ResourceGroupName)
62+
return
6663
}
6764
}
6865
}

frontend/pkg/frontend/routes.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ func (f *Frontend) routes() *MiddlewareMux {
6868
mux.Handle(
6969
MuxPattern(http.MethodGet, PatternSubscriptions, PatternResourceGroups, PatternProviders, api.ClusterResourceTypeName),
7070
postMuxMiddleware.HandlerFunc(f.ArmResourceList))
71+
mux.Handle(
72+
MuxPattern(http.MethodGet, PatternSubscriptions, PatternResourceGroups, PatternProviders, PatternClusters, api.NodePoolResourceTypeName),
73+
postMuxMiddleware.HandlerFunc(f.ArmResourceList))
7174

7275
// Resource ID endpoints
7376
// Request context holds an azcorearm.ResourceID

0 commit comments

Comments
 (0)