Apprio Azure Infrastructure
This library extends functionality and creates a runtime for the Azure Management SDK for .NET. Inpsiration was borrowed heavily from Jamie Phillips. Please consider buying Jamie a coffee.
Features
- Hosting runtime to execute your infrastructure code
- Dependency injection to create testable infrastructure code
- Naming provider so you can enforce strict naming conventions for similar Azure resources
- Tag provider to standardize tags across provisioned resources
- Region provider so you can run the same deployment to multiple regions (for active-active or active-passive setups)
- Region helper for parsing Azure regions
Getting Started
Create a new .NET 5.0 console app
- Once you've created your app, add your appsettings.json files. These appsettings.json files should contain non-sensitive configuration settings for your deployment. These files are evaluated where you can have environment settings layered over top of the default settings. Tree structure should look similar to the following:
- appsettings.json
- appsettings.dev.json
- appsettings.prod.json
- appsettings.qa.json
- App settings file should follow a structure similar to this (feel free to add your own settings!):
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Organization": "exampl",
"Product": "secureTokens",
"RegionOptions": {
"App": [
"USEast",
"USWest"
],
"Shared": "USCentral"
},
"SecurityOptions": {
"SecurityGroupObjectId": "00000000-0000-0000-0000-000000000000",
"Domain": "example.com"
},
"TagOptions": {
"Tags": [
{
"Name": "owner",
"Value": "me@example.com"
},
{
"Name": "maintainer",
"Value": "you@example.com"
}
]
}
}
- Set up your IaC jobs. In most cases you'll minimally want two jobs: create and destroy. See the following two templates for a basic implementation: Create
using System.Threading.Tasks;
using Microsoft.Azure.Management.AppService.Fluent;
using Microsoft.Azure.Management.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Models;
using Apprio.Azure.Infrastructure.Abstractions;
using Apprio.Azure.Infrastructure.Models;
public class CreateJob : IJob
{
private readonly IAzure azure;
private readonly IAzureOptions azureOptions;
private readonly ILogger<CreateJob> logger;
private readonly INameProvider nameProvider;
private readonly IRegionProvider regionProvider;
private readonly SecurityOptions securityOptions;
private readonly ITagProvider tagProvider;
public CreateJob(
IAzure azure,
IAzureOptions azureOptions,
ILogger<CreateJob> logger,
INameProvider nameProvider,
IRegionProvider regionProvider,
IOptions<SecurityOptions> securityOptions,
ITagProvider tagProvider)
{
this.azure = azure;
this.azureOptions = azureOptions;
this.logger = logger;
this.nameProvider = nameProvider;
this.regionProvider = regionProvider;
this.securityOptions = securityOptions.Value;
this.tagProvider = tagProvider;
}
public async Task RunAsync(IJobContext context)
{
var tags = tagProvider.CreateDefaultTags();
logger.LogInformation("Provisioning shared resources");
var region = regionProvider.GetSharedRegion();
logger.LogInformation("Provisioning shared resource group");
var resourceGroup = await azure.ResourceGroups
.Define(nameProvider.ForResourceGroup(region))
.WithRegion(region)
.WithTags(tags)
.CreateAsync();
logger.LogInformation($"Provisioning shared resources in `{region}` has completed");
}
}
Destroy
using System.Threading.Tasks;
using Microsoft.Azure.Management.AppService.Fluent;
using Microsoft.Azure.Management.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Models;
using Apprio.Azure.Infrastructure.Abstractions;
using Apprio.Azure.Infrastructure.Models;
public class DestroyJob : IJob
{
private readonly IAzure azure;
private readonly IAzureOptions azureOptions;
private readonly ILogger<CreateJob> logger;
private readonly INameProvider nameProvider;
private readonly IRegionProvider regionProvider;
private readonly SecurityOptions securityOptions;
private readonly ITagProvider tagProvider;
public CreateJob(
IAzure azure,
IAzureOptions azureOptions,
ILogger<CreateJob> logger,
INameProvider nameProvider,
IRegionProvider regionProvider,
IOptions<SecurityOptions> securityOptions,
ITagProvider tagProvider)
{
this.azure = azure;
this.azureOptions = azureOptions;
this.logger = logger;
this.nameProvider = nameProvider;
this.regionProvider = regionProvider;
this.securityOptions = securityOptions.Value;
this.tagProvider = tagProvider;
}
public async Task RunAsync(IJobContext context)
{
logger.LogInformation("Deleting shared resources");
var region = regionProvider.GetSharedRegion();
logger.LogInformation($"Deleting app resources in `{region}`");
await azure.ResourceGroups.DeleteByNameAsync(nameProvider.ForResourceGroup(region));
logger.LogInformation($"Deleting shared resources in `{region}` has completed");
}
}
- After setting up your jobs, set up your job host:
using Apprio.Azure.Infrastructure;
using Apprio.Azure.Infrastructure.Abstractions;
using Apprio.Azure.Infrastructure.Models;
using Apprio.Azure.Infrastructure.Services;
public static class Program
{
public static void Main(string[] args)
{
// register jobs fluently
var create = JobDescriptor.WithType<CreateJob>.WithCommand("create");
var destroy = JobDescriptor.WithType<DestroyJob>.WithCommand("destroy");
// create orchestrator
var jobOrchestrator = new JobOrchestrator();
// add jobs to orchestrator
jobOrchestrator.Add(create, destroy);
// inject any custom services
jobOrchestrator.OnConfiguringServices += (sender, services) =>
{
// todo: add any necessary services for DI
services.AddExampleServices();
};
// set up service host and run job
JobHost.Run(
jobOrchestrator,
create.Command, // default command if none is provided to the program
c => AzureOptions.FromEnvironmentVariables, // get azure credentials from environment variables
args);
}
}