OdeToCode IC Logo

Working with Azure Management REST APIs

Tuesday, February 6, 2018

In previous posts we looked at how to choose an approach for working with the management APIs, and how to setup a service principal name to authenticate an application that invokes the APIs.

In that first post we decided (assuming "we" are .NET developers) that we want to work with the APIs using an SDK instead of building our own HTTP messages using HttpClient. However, even here there are choices in which SDK to use. In this post we will compare and contrast two SDKs, and I’ll offer some tips I’ve learned in figuring out how the SDKs work. Before we dig in, I will say that having the REST API reference readily available is still useful even when working with the higher level SDKs. It is often easier to find the available operations for a given resource and what the available parameters control by looking at the reference directly.

The SDKs for working with the management APIs from C# can be broadly categorized into either generated SDKs, or fluent SDKs. Generated SDKs cover nearly all operations in the management APIs and Microsoft creates these libraries by generating C# code from metadata (OpenAPI specs, formerly known as Swagger). In the other category, human beings craft the fluent version of the libraries to make code readable and operations discoverable, although you won’t find a fluent package for every API area.

The Scenario

In this post we’ll work with the Azure SQL management APIs.  Imagine we want to programmatically change the Pricing tier of an Azure SQL instance to scale a database up and down. Scaling up to a higher pricing tier gives the database more DTUs to work with. Scaling down gives the database fewer DTUs, but also is less expensive. If you've worked with Azure SQL, you'll know DTUs are the frustratingly vague measurement of how many resources an Azure SQL instance can utilize to process your workload. More DTUs == more powerful SQL database.

The Generated SDKs

The Azure SQL management SDK is in the Microsoft.Azure.Management.Sql NuGet package, which is still in preview. I prefer this package to the package with the word Windows in the name, as this package is actively updated. The management packages support both .NET Core (netstandard 1.4), and the .NET framework.

The first order of business is to generate a token that will give the app an identity and authorize the app to work with the management APIs. You can obtain the token using raw HTTP calls, or use the Microsoft.IdentityModel.Clients.ActiveDirectory package, also known as ADAL (Active Directory Authentication Library). You’ll need your application’s ID and secret, which are setup in the previous post when registering the app with Azure AD, as well as your tenant ID, also known as the directory ID, which is the ID of your Azure AD instance. By the way, have you noticed the recurring theme in these post of having two names for every important object?

Take the above ingredients and cook them in an AuthenticationContext to produce an bearer token:

    public async Task<TokenCredentials> MakeTokenCredentials()
    {         
        var appId = "798dccc9-....-....-....-............";
        var appSecret = "8a9mSPas....................................=";
        var tenantId = "11be8607-....-....-....-............";
        var authority = $"https://login.windows.net/{tenantId}";
        var resource = ""https://management.azure.com/";
    
        var authContext = new AuthenticationContext(authority);
        var credential = new ClientCredential(appId, appSecret);
        var authResult = await authContext.AcquireTokenAsync(resource, credential);            
        return new TokenCredentials(authResult.AccessToken, "Bearer");
    }
    

In the above example, I’ve hard coded all the pieces of information to make the code easy to read, but you’ll certainly make a parameter object for flexibility. Note the authority will be login.windows.net for the Azure global cloud, plus your tenantId, although I believe you can also use your friendly Azure AD domain name here also. The resource parameter for AcquireTokenAsync will always be management.azure.com, unless you are in one of the special Azure clouds.

With credentials in hand, the gateway to the SQL management APIs is a SqlManagementClient class. Management classes are consistently named across the various SDKs for the different APIs. For example, to manage App Services there is a WebSiteManagementClient in the App Service NuGet. All these service client classes build on HttpClient and provide some extensibility points. For example, the manager constructors all allow you to pass in a DelegatingHandler which you can use to inspect or modify HTTP request and response messages as they work their way through the underlying HttpClient pipeline.

Here’s a class that demonstrates how to use the SqlManagementClient to move an Azure SQL Instance into the cheapest standard plan with the fewest DTUs. This plan is the "S0" plan.

    public class RestApproach
    {
        private SqlManagementClient client;
    
        public RestApproach(TokenCredentials credentials, string subscriptionId)
        {
            client = new SqlManagementClient(credentials, new LoggingHandler());
            client.SubscriptionId = subscriptionId;
    
        }
    
        public async Task SetDtus()
        {
            var resourceGroupName = "rgname";
            var serverName = "server";
            var databaseName = "targetdatabase"; 
            
            var database = await client.Databases.GetAsync(resourceGroupName, serverName, databaseName);
            var updateModel = new DatabaseUpdate(requestedServiceObjectiveName: "S0");            
            var result = await client.Databases.UpdateAsync(resourceGroupName, serverName, databaseName, updateModel);
            
            Console.WriteLine($"Database {database.Name} is {database.ServiceLevelObjective}");
            Console.WriteLine($"Updating {database.Name} to S0");           
        }
    }

A couple notes on the code.

First, all management APIs are grouped into properties on the management class. Use client.Databases for database operations, and client.Server for server operations, and so on. This might feel odd at first.

Secondly, we have to face the terminology beast yet again. What we might think of as pricing tiers or DTU settings in the portal will be referred to as “service level objectives”. If you do any work with the Azure resource manager or resource templates, I’m sure you’ve already experienced the mapping of engineering terms to UI terms.

Thirdly, even though the database update model has a ServiceLevelObjective property, to change a service level you need to use the RequestedServiceObjectName property on the update model. This is one of those scenarios where reading the REST API documentation can help, because the properties will map to the parameters you see in the docs by name, and the docs are clear about what each parameter can do.

Fourthly, some operations, like setting the service level of a SQL database, require specific string values like “S0”. There is always an API you can use to retrieve the legal values that takes into account your location. For service levels, you can also use the CLI to see a list.

    λ az sql db list-editions --location "EastUS" 
        --query "[].supportedServiceLevelObjectives[].name"
    [
        "Basic",
        "S0",
        "S1",
        ...
        ...
        ...   
        "DW30000c",
        
    

While the generated SDK packages will give you some friction until your mental model adjusts, they are an effective approach to using the management SDKs. There is no need to use HttpClient directly, but if you need the flexibility, the HttpClient instance is available from the manager class.

The Fluent Approach

The fluent version of the SQL management SDK is in the Microsoft.Azure.Management.Sql.Fluent package. You can take any management package and add the word “Fluent” on the end to see if there is a fluent alternative. You’ll also want to reference Microsoft.Azure.Management.ResourceManager.Fluent for writing easier authentication code.

The first step again is to put together some credentials:

    public AzureCredentials MakeAzureCredentials(string subscriptionId)
    {
        var appId = "798dccc9-....-....-....-............";
        var appSecret = "8a9mSPas....................................=";
        var tenantId = "11be8607-....-....-....-............";
        var environment = AzureEnvironment.AzureGlobalCloud;
    
        var credentials = new AzureCredentialsFactory()
                                .FromServicePrincipal(appId, appSecret, tenantId, environment);
    
        return credentials;
    }

Notice the fluent API requires a bit less code. The API is smart enough to determine the login endpoints and management endpoints based on the selected AzureEnvironment (there’s the global cloud, but also the specialized clouds like the German cloud, U.S. Federal cloud, etc).

Now, here is the fluent version of setting the service level to compare with the previous code.

    public class FluentApproach
    {
        private ISqlManager manager;
    
        public FluentApproach(AzureCredentials credentials, string subscriptionId)
        {
            manager = SqlManager.Authenticate(credentials, subscriptionId);
        }
    
        public async Task SetDtus()
        {
            var resourceGroupName = "rgname";
            var serverName = "servername";
            var databaseName = "databasename"; // case senitive            
            var database =                                     
                    (await
                      manager.SqlServers
                             .GetByResourceGroupAsync(resourceGroupName, serverName))
                             .Databases.Get(databaseName);
                                
            await database.Update()
                          .WithServiceObjective("S2")
                          .ApplyAsync();
    
            Console.WriteLine($"Database {database.Name} was {database.ServiceLevelObjective}");
            Console.WriteLine($"Updating {database.Name} to S2");
        }
    }

The fluent API uses a SqlManager class. Instead of grouping all operations on the manager, you can now think in the same hierarchy as the resources you manage. Instead of figuring out which properties to set on an update model, the fluent API allows for method chains that build up a data structure. As an aside, I still haven’t found an aesthetic approach to formatting chained methods with the await keyword, so it is tempting to use the synchronous methods. However, I still prefer the fluent API to the code-genreated API as the code is easier to read and write.

Summary

You won’t find many examples of using the management APIs on the web, but the APIs can be an incredibly useful tool for automation. ARM templates are arguably a better approach for provisioning and updating resources, and CLI tools are certainly a better approach for interactions up to a medium amount of complexity. But, for services that combine resource management with logic and hueristics, the APIs via an SDK is the best combination.