diff --git a/ARMExplorer.csproj b/ARMExplorer.csproj
index 19aacc7..372b378 100644
--- a/ARMExplorer.csproj
+++ b/ARMExplorer.csproj
@@ -376,6 +376,8 @@
+
+
diff --git a/Controllers/ArmRepository.cs b/Controllers/ArmRepository.cs
index 55c1b54..a7c763e 100644
--- a/Controllers/ArmRepository.cs
+++ b/Controllers/ArmRepository.cs
@@ -6,6 +6,7 @@
using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using ARMExplorer.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -14,6 +15,7 @@ namespace ARMExplorer.Controllers
public class ArmRepository : IArmRepository
{
private readonly IHttpClientWrapper _clientWrapper;
+ private readonly int _maxNextLinkDepth = 5;
public ArmRepository(IHttpClientWrapper clientWrapper)
{
@@ -40,17 +42,63 @@ public async Task> GetSubscriptionIdsAsync(HttpRequestMessage requ
return subscriptionIds;
}
+ private static bool AddResourceToList(IEnumerable resources, ISet allResources)
+ {
+ var initalCount = allResources.Count;
+
+ foreach (var resource in resources)
+ {
+ allResources.Add(resource);
+ }
+
+ var updatedCount = allResources.Count;
+
+ return updatedCount > initalCount;
+ }
+
+ private async Task> GetResources(HttpRequestMessage requestMessage, string getResourcesUrl)
+ {
+ var allResources = new HashSet();
+ var currentNextLinkDepth = 0;
+
+ while (!string.IsNullOrEmpty(getResourcesUrl))
+ {
+ var response = await GetAsync(requestMessage, getResourcesUrl);
+ response.EnsureSuccessStatusCode();
+ var armResourceListResult = await response.Content.ReadAsAsync();
+
+ var newResourceFound = AddResourceToList(armResourceListResult.Value, allResources);
+
+ // ARM API returns the same skiptoken and resources repeatedly when there are no more resources. To avoid infinite cycle break when
+ // 1. No new resource was found in the current response or
+ // 2. Limit the max number of links to follow to _maxNextLinkDepth or
+ // 3. When nextLink is empty
+
+ if (!newResourceFound)
+ {
+ break;
+ }
+
+ if (currentNextLinkDepth++ > _maxNextLinkDepth)
+ {
+ break;
+ }
+
+ getResourcesUrl = armResourceListResult.NextLink;
+ }
+
+ return allResources;
+ }
+
public async Task> GetProviderNamesFor(HttpRequestMessage requestMessage, string subscriptionId)
{
- var response = await GetResourcesForAsync(requestMessage, subscriptionId);
- response.EnsureSuccessStatusCode();
- dynamic resources = await response.Content.ReadAsAsync();
- JArray values = resources.value;
+ var initialGetResourcesUrl = string.Format(Utils.ResourcesTemplate, HyakUtils.CSMUrl, subscriptionId, Utils.CSMApiVersion);
+ var resources = await GetResources(requestMessage, initialGetResourcesUrl);
var uniqueProviders = new HashSet(StringComparer.OrdinalIgnoreCase);
- foreach (dynamic value in values)
+
+ foreach (var resource in resources)
{
- string id = value.id;
- var match = Regex.Match(id, "/subscriptions/.*?/resourceGroups/(.*?)/providers/(.*?)/(.*?)/");
+ var match = Regex.Match(resource.Id, "/subscriptions/.*?/resourceGroups/(.*?)/providers/(.*?)/(.*?)/");
if (match.Success)
{
var provider = match.Groups[2].Value.ToUpperInvariant();
@@ -63,15 +111,13 @@ public async Task> GetProviderNamesFor(HttpRequestMessage reques
public async Task>>> GetProvidersFor(HttpRequestMessage requestMessage, string subscriptionId)
{
- var response = await GetResourcesForAsync(requestMessage, subscriptionId);
- response.EnsureSuccessStatusCode();
-
- dynamic resources = await response.Content.ReadAsAsync();
- JArray values = resources.value;
+ var initialGetResourcesUrl = string.Format(Utils.ResourcesTemplate, HyakUtils.CSMUrl, subscriptionId, Utils.CSMApiVersion);
+ var resources = await GetResources(requestMessage, initialGetResourcesUrl);
var result = new Dictionary>>();
- foreach (dynamic value in values)
+
+ foreach (var resource in resources)
{
- string id = value.id;
+ string id = resource.Id;
var match = Regex.Match(id, "/subscriptions/.*?/resourceGroups/(.*?)/providers/(.*?)/(.*?)/");
if (match.Success)
{
@@ -116,10 +162,10 @@ private async Task GetSubscriptionsAsync(HttpRequestMessage
return await _clientWrapper.SendAsync(requestMessage, sendRequest);
}
- private async Task GetResourcesForAsync(HttpRequestMessage requestMessage, string subscriptionId)
+ private async Task GetAsync(HttpRequestMessage requestMessage, string url)
{
- var sendRequest = new HttpRequestMessage(HttpMethod.Get, string.Format(Utils.ResourcesTemplate, HyakUtils.CSMUrl, subscriptionId, Utils.CSMApiVersion));
- return await _clientWrapper.SendAsync(requestMessage, sendRequest);
+ var sendRequest = new HttpRequestMessage(HttpMethod.Get, url);
+ return await _clientWrapper.ExecuteAsync(requestMessage, sendRequest);
}
}
}
\ No newline at end of file
diff --git a/Model/ArmResource.cs b/Model/ArmResource.cs
new file mode 100644
index 0000000..78f65c1
--- /dev/null
+++ b/Model/ArmResource.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace ARMExplorer.Model
+{
+ public class ArmResource : IEquatable
+ {
+ [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Id { get; set; }
+ // other fields ignored
+
+ public bool Equals(ArmResource other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return string.Equals(Id, other.Id, StringComparison.OrdinalIgnoreCase);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((ArmResource) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return Id != null ? Id.GetHashCode() : 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Model/ArmResourceListResult.cs b/Model/ArmResourceListResult.cs
new file mode 100644
index 0000000..61b52f1
--- /dev/null
+++ b/Model/ArmResourceListResult.cs
@@ -0,0 +1,13 @@
+using System.Collections.ObjectModel;
+
+namespace ARMExplorer.Model
+{
+ public class ArmResourceListResult
+ {
+ [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public Collection Value { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("nextLink", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string NextLink { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Tests/WebApiTests/MockHttpClientWrapper.cs b/Tests/WebApiTests/MockHttpClientWrapper.cs
index 6ddada8..3062eca 100644
--- a/Tests/WebApiTests/MockHttpClientWrapper.cs
+++ b/Tests/WebApiTests/MockHttpClientWrapper.cs
@@ -30,7 +30,20 @@ public Task SendAsync(HttpRequestMessage requestMessage, Ht
public Task ExecuteAsync(HttpRequestMessage requestMessage, HttpRequestMessage executeRequest)
{
- throw new NotImplementedException();
+ var responseMessage = new HttpResponseMessage(HttpStatusCode.OK);
+ string filePath;
+ if (executeRequest.RequestUri.ToString().Contains("resources"))
+ {
+ filePath = Path.Combine(new DirectoryInfo(Directory.GetCurrentDirectory()).FullName, Path.Combine("WebApiTests", "data", "resourcesForsubscription.json"));
+ }
+ else
+ {
+ filePath = Path.Combine(new DirectoryInfo(Directory.GetCurrentDirectory()).FullName, Path.Combine("WebApiTests", "data", "subscriptions.json"));
+ }
+ responseMessage.Content = new StringContent(File.ReadAllText(filePath), Encoding.UTF8, "application/json");
+ var response = new TaskCompletionSource();
+ response.SetResult(responseMessage);
+ return response.Task;
}
}
}
\ No newline at end of file