[release-11.5.4] Azure: Resource picker improvements (#103638)
Azure: Resource picker improvements (#101462)
* Update resource group query
- Updates the resource groups query to support users/apps with restricted permissions
* Update resources request to be paginated
- Also order by name
- Add tests
* Update test
(cherry picked from commit 4595df9be6)
This commit is contained in:
+46
-2
@@ -66,7 +66,7 @@ describe('AzureMonitor resourcePickerData', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('makes multiple requests when arg returns a skipToken and passes the right skipToken to each subsequent call', async () => {
|
||||
it('makes multiple requests for subscriptions when arg returns a skipToken and passes the right skipToken to each subsequent call', async () => {
|
||||
const response1 = {
|
||||
...createMockARGSubscriptionResponse(),
|
||||
$skipToken: 'skipfirst100',
|
||||
@@ -82,6 +82,48 @@ describe('AzureMonitor resourcePickerData', () => {
|
||||
expect(postBody.options.$skipToken).toEqual('skipfirst100');
|
||||
});
|
||||
|
||||
it('makes multiple requests for resource groups when arg returns a skipToken and passes the right skipToken to each subsequent call', async () => {
|
||||
const subscriptionResponse = createMockARGSubscriptionResponse();
|
||||
const resourceGroupResponse1 = {
|
||||
...createMockARGResourceGroupsResponse(),
|
||||
$skipToken: 'skipfirst100',
|
||||
};
|
||||
const resourceGroupResponse2 = createMockARGResourceGroupsResponse();
|
||||
const { resourcePickerData, postResource } = createResourcePickerData([
|
||||
subscriptionResponse,
|
||||
resourceGroupResponse1,
|
||||
resourceGroupResponse2,
|
||||
]);
|
||||
|
||||
await resourcePickerData.getResourceGroupsBySubscriptionId('1', 'metrics');
|
||||
|
||||
expect(postResource).toHaveBeenCalledTimes(3);
|
||||
const secondCall = postResource.mock.calls[2];
|
||||
const [_, postBody] = secondCall;
|
||||
expect(postBody.options.$skipToken).toEqual('skipfirst100');
|
||||
});
|
||||
|
||||
it('makes multiple requests for resources when arg returns a skipToken and passes the right skipToken to each subsequent call', async () => {
|
||||
const subscriptionResponse = createMockARGSubscriptionResponse();
|
||||
const resourcesResponse1 = {
|
||||
...createARGResourcesResponse(),
|
||||
$skipToken: 'skipfirst100',
|
||||
};
|
||||
const resourcesResponse2 = createARGResourcesResponse();
|
||||
const { resourcePickerData, postResource } = createResourcePickerData([
|
||||
subscriptionResponse,
|
||||
resourcesResponse1,
|
||||
resourcesResponse2,
|
||||
]);
|
||||
|
||||
await resourcePickerData.getResourcesForResourceGroup('resourceGroupURI', 'metrics');
|
||||
|
||||
expect(postResource).toHaveBeenCalledTimes(3);
|
||||
const secondCall = postResource.mock.calls[2];
|
||||
const [_, postBody] = secondCall;
|
||||
expect(postBody.options.$skipToken).toEqual('skipfirst100');
|
||||
});
|
||||
|
||||
it('returns a concatenates a formatted array of subscriptions when there are multiple pages from arg', async () => {
|
||||
const response1 = {
|
||||
...createMockARGSubscriptionResponse(),
|
||||
@@ -129,7 +171,9 @@ describe('AzureMonitor resourcePickerData', () => {
|
||||
const firstCall = postResource.mock.calls[0];
|
||||
const [path, postBody] = firstCall;
|
||||
expect(path).toEqual('resourcegraph/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01');
|
||||
expect(postBody.query).toContain("type == 'microsoft.resources/subscriptions/resourcegroups'");
|
||||
expect(postBody.query).toContain(
|
||||
'extend resourceGroupURI = strcat("/subscriptions/", subscriptionId, "/resourcegroups/", resourceGroup)'
|
||||
);
|
||||
expect(postBody.query).toContain("where subscriptionId == '123'");
|
||||
});
|
||||
|
||||
|
||||
@@ -187,18 +187,23 @@ export default class ResourcePickerData extends DataSourceWithBackend<
|
||||
type: ResourcePickerQueryType
|
||||
): Promise<ResourceRowGroup> {
|
||||
// We can use subscription ID for the filtering here as they're unique
|
||||
// The logic of this query is:
|
||||
// Retrieve _all_ resources a user/app registration/identity has access to
|
||||
// Filter by the namespaces that support metrics
|
||||
// Filter to resources contained within the subscription
|
||||
// Conduct a left-outer join on the resourcecontainers table to allow us to get the case-sensitive resource group name
|
||||
// Return the count of resources in a group, the URI, and name of the group in ascending order
|
||||
const query = `
|
||||
resources
|
||||
| join kind=inner (
|
||||
ResourceContainers
|
||||
| where type == 'microsoft.resources/subscriptions/resourcegroups'
|
||||
| project resourceGroupURI=id, resourceGroupName=name, resourceGroup, subscriptionId
|
||||
) on resourceGroup, subscriptionId
|
||||
|
||||
${await this.filterByType(type)}
|
||||
| where subscriptionId == '${subscriptionId}'
|
||||
| summarize count() by resourceGroupName, resourceGroupURI
|
||||
| order by resourceGroupURI asc`;
|
||||
resources
|
||||
${await this.filterByType(type)}
|
||||
| where subscriptionId == '${subscriptionId}'
|
||||
| extend resourceGroupURI = strcat("/subscriptions/", subscriptionId, "/resourcegroups/", resourceGroup)
|
||||
| join kind=leftouter (resourcecontainers
|
||||
| where type =~ 'microsoft.resources/subscriptions/resourcegroups'
|
||||
| project resourceGroupName=name, resourceGroupURI=tolower(id)) on resourceGroupURI
|
||||
| project resourceGroupName=iff(resourceGroupName != "", resourceGroupName, resourceGroup), resourceGroupURI
|
||||
| summarize count() by resourceGroupName, resourceGroupURI
|
||||
| order by tolower(resourceGroupName) asc `;
|
||||
|
||||
let resourceGroups: RawAzureResourceGroupItem[] = [];
|
||||
let allFetched = false;
|
||||
@@ -240,13 +245,30 @@ export default class ResourcePickerData extends DataSourceWithBackend<
|
||||
// We use resource group URI for the filtering here because resource group names are not unique across subscriptions
|
||||
// We also add a slash at the end of the resource group URI to ensure we do not pull resources from a resource group
|
||||
// that has a similar naming prefix e.g. resourceGroup1 and resourceGroup10
|
||||
const { data: response } = await this.makeResourceGraphRequest<RawAzureResourceItem[]>(`
|
||||
resources
|
||||
| where id hasprefix "${resourceGroupUri}/"
|
||||
${await this.filterByType(type)}
|
||||
`);
|
||||
const query = `
|
||||
resources
|
||||
| where id hasprefix "${resourceGroupUri}/"
|
||||
${await this.filterByType(type)}
|
||||
| order by tolower(name) asc`;
|
||||
|
||||
return response.map((item) => {
|
||||
let resources: RawAzureResourceItem[] = [];
|
||||
let allFetched = false;
|
||||
let $skipToken = undefined;
|
||||
while (!allFetched) {
|
||||
// The response may include several pages
|
||||
let options: Partial<AzureResourceGraphOptions> = {};
|
||||
if ($skipToken) {
|
||||
options = {
|
||||
$skipToken,
|
||||
};
|
||||
}
|
||||
const resourceResponse = await this.makeResourceGraphRequest<RawAzureResourceItem[]>(query, 1, options);
|
||||
resources = resources.concat(resourceResponse.data);
|
||||
$skipToken = resourceResponse.$skipToken;
|
||||
allFetched = !$skipToken;
|
||||
}
|
||||
|
||||
return resources.map((item) => {
|
||||
const parsedUri = parseResourceURI(item.id);
|
||||
if (!parsedUri || !parsedUri.resourceName) {
|
||||
throw new Error('unable to fetch resource details');
|
||||
|
||||
Reference in New Issue
Block a user