Skip to content

Commit

Permalink
Added a quick start and the ability to run report in parallel or (#1)
Browse files Browse the repository at this point in the history
sequential

When running in parallel I ran into several exceptions and the output
looked incomplete.

Co-authored-by: Mårten Rånge <[email protected]>
  • Loading branch information
mrange and Mårten Rånge authored Nov 10, 2023
1 parent 1ddb7a3 commit 03c81bb
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 12 deletions.
40 changes: 40 additions & 0 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Quick Start

## Running ARI Successfully for the First Time

### Setup Azure App Registration

1. Begin by creating an `App Registration` in Azure Entra for the report generator. This ensures that the report generator has precisely the required access, such as organization-wide read permissions or access to a limited set of subscriptions.

2. Assign the API permission `https://graph.microsoft.com/Organization.Read.All` to the created `App Registration`.

3. In my tenant, Admin consent is required for this permission.

4. Add a role that allows the `App Registration` to read an organization. You can do this under `Subscription` management in Azure and `Access Control (IAM)`. Add `Role Assignment`, find the `App Registration` and give it `Read` access.

5. Assign a secret to the `App Registration` and make a note of this secret.

With the Azure App Registration now configured, we are ready to proceed.

### Configure ARI for Execution

1. Create a dedicated folder for the generated report.

2. Set the environment variable `AZURE_TENANT_ID` to the tenant ID (found in the `App Registration` overview for your app).

3. Set the environment variable `AZURE_CLIENT_ID` to the client ID (found in the `App Registration` overview for your app).

4. Set the environment variable `AZURE_CLIENT_SECRET` to the secret noted earlier.

5. Set the environment variable `AZURE_AUTHORITY_HOST` to `https://login.microsoftonline.com/`.

## Run ARI

Assuming all the environment variables are correctly set, follow these steps:

```bash
dotnet tool install --global ARI
ari <AZURE_TENANT_ID> <FOLDER_FOR_REPORT>
```

By following these steps, you should be able to run ARI successfully for the first time. If you encounter any issues, double-check the Azure App Registration setup and ensure that the environment variables are accurately configured.
50 changes: 41 additions & 9 deletions src/ARI/Commands/InventoryCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ARI.Extensions;
using ARI.Models.Tenant.Subscription.ResourceGroup.Resource;
using Cake.Common.IO;

namespace ARI.Commands;
Expand All @@ -16,21 +17,24 @@ public override async Task<int> ExecuteAsync(CommandContext context, InventorySe
{
var sw = System.Diagnostics.Stopwatch.StartNew();
var modified = DateTimeOffset.UtcNow;
var markDownFileName = settings.MarkdownName + ".md";

Logger.LogInformation("TenantId: {TenantId}", settings.TenantId);
Logger.LogInformation("OutputPath: {OutputPath}", settings.OutputPath);
Logger.LogInformation("Generate report in parallel: {GenerateInParallel}", settings.SkipTenantOverview);
Logger.LogInformation("Using markdown filename: {MarkDownFileName}", markDownFileName);

var tenant = await TenantService.GetTenant(settings.TenantId);

var targetPath = (!settings.SkipTenantOverview)
? settings.OutputPath.Combine(tenant.DefaultDomain)
: settings.OutputPath;

Logger.LogInformation("Cleaning directory {TargetPath}...", targetPath);
CakeContext.CleanDirectory(targetPath);
Logger.LogInformation("Done cleaning directory {TargetPath}.", targetPath);

using var writer = CakeContext
.OpenIndexWrite(targetPath);
.OpenIndexWrite(targetPath, markDownFileName);

if (!settings.SkipTenantOverview)
{
Expand All @@ -57,12 +61,13 @@ await writer.AddFrontmatter(

await writer.AddChildrenIndex(subscriptions);

await Parallel.ForEachAsync(
await ForEachAsync(
settings,
subscriptions,
async (subscription, ct) =>
{
using var writer = CakeContext
.OpenIndexWrite(targetPath, subscription, out var subscriptionPath);
.OpenIndexWrite(targetPath, subscription, markDownFileName, out var subscriptionPath);

await writer.AddFrontmatter(
modified,
Expand All @@ -81,12 +86,13 @@ await writer.AddFrontmatter(

await writer.AddChildrenIndex(resourceGroups);

await Parallel.ForEachAsync(
await ForEachAsync(
settings,
resourceGroups,
async (resourceGroup, ct) =>
{
using var writer = CakeContext
.OpenIndexWrite(subscriptionPath, resourceGroup, out var resourceGroupPath);
.OpenIndexWrite(subscriptionPath, resourceGroup, markDownFileName, out var resourceGroupPath);

await writer.AddFrontmatter(
modified,
Expand Down Expand Up @@ -140,12 +146,13 @@ await writer.WriteLineAsync(
}


await Parallel.ForEachAsync(
await ForEachAsync(
settings,
resources,
async (resource, ct) =>
{
using var writer = CakeContext
.OpenIndexWrite(resourceGroupPath, resource, out var resourcePath);
.OpenIndexWrite(resourceGroupPath, resource, markDownFileName, out var resourcePath);

await writer.AddFrontmatter(
modified,
Expand All @@ -165,10 +172,35 @@ await writer.AddFrontmatter(

sw.Stop();
Logger.LogInformation("Processed {SubscriptionCount} in {Elapsed}", subscriptions.Count, sw.Elapsed);

return 0;
}

async Task ForEachAsync<TSource>(
InventorySettings settings,
IEnumerable<TSource> source,
Func<TSource, CancellationToken, ValueTask> body)
{
if (settings.RunInParallel)
{
await Parallel.ForEachAsync(source, body);
}
else
{
using var cts = new CancellationTokenSource();
var ct = cts.Token;
foreach (var v in source)
{
await body(v, ct);
if (ct.IsCancellationRequested)
{
return;
}
}

return;
}

}
public InventoryCommand(
ICakeContext cakeContext,
ILogger<InventoryCommand> logger,
Expand Down
6 changes: 6 additions & 0 deletions src/ARI/Commands/Settings/InventorySettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@ public class InventorySettings : CommandSettings

[CommandOption("--skip-tenant-overview")]
public bool SkipTenantOverview { get; set; }

[CommandOption("--run-in-parallel")]
public bool RunInParallel { get; set; }

[CommandOption("--markdown-name")]
public string MarkdownName { get; set; } = "index";
}
8 changes: 5 additions & 3 deletions src/ARI/Extensions/CakeContextExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ public static TextWriter OpenIndexWrite(
this ICakeContext cakeContext,
DirectoryPath parentPath,
AzureResourceBase azureResource,
string markDownFileName,
out DirectoryPath targetPath
)
{
targetPath = parentPath.Combine(azureResource.PublicId);

return cakeContext.OpenIndexWrite(targetPath);
return cakeContext.OpenIndexWrite(targetPath, markDownFileName);
}

public static TextWriter OpenIndexWrite(
this ICakeContext cakeContext,
DirectoryPath targetPath
DirectoryPath targetPath,
string markDownFileName
)
{
lock (cakeContext.FileSystem)
Expand All @@ -27,7 +29,7 @@ DirectoryPath targetPath

var stream = cakeContext
.FileSystem
.GetFile(targetPath.CombineWithFilePath("index.md"))
.GetFile(targetPath.CombineWithFilePath(markDownFileName))
.OpenWrite();

var writer = new StreamWriter(
Expand Down

0 comments on commit 03c81bb

Please sign in to comment.