Muitas vezes temos a necessidade de integrar as APIs da Azure em nossas aplicações e fluxos de trabalho. A Azure nos oferece uma série de APIs especializadas!

Hoje vamos fazer algumas chamadas a API de cost management, com objetivo de obter os gastos diários de qualquer subscription pertencente a sua tenant. Inicialmente vamos registrar uma nova application em nosso Azure AD.

Nossa aplicação ira interagir com o Azure por conta própria, não necessitando que nenhum usuário esteja autenticado. Por isso, será preciso gerar um certificado ou segredo para essa aplicação.

Copie o valor do segredo. Além disso, devemos também salvar outros valores referentes a nossa tenant Id, subscription Id e client Id.

Para que nossa aplicação possa ler os valores dos gastos diários, devemos conceder a permissão de Reader da nossa subscription para nossa aplicação. Vamos utilizar o Azure RBAC!

Dentro da subscription, devemos ir menu "Access control (IAM)" e clicar em "Add role assignment".

Basta selecionarmos a permissão de Reader e a nossa aplicação como alvo da atribuição.

Com isso pronto, já podemos criar nosso aplicação. Vamos criar um novo diretório, uma solution do .NET e um projeto web básico.

mkdir AzureCostManagement
dotnet new sln
dotnet new web -o AzureCostManagement.Api

Devemos adicionar o projeto a solution recém criada.

dotnet sln add AzureCostManagement.Api/AzureCostManagement.Api.csproj

Podemos adicionar algumas configurações ao appsettings.json do nosso projeto.

 "Application": {
    "AzureAdLoginUrlResource" : "https://login.microsoftonline.com",
    "AzureAdLoginUrlPath" : "/{0}/oauth2/v2.0/token",
    "AzureCostManagementUrlResource": "https://management.azure.com",
    "AzureCostManagementUrlPath": "/subscriptions/{0}/providers/Microsoft.CostManagement/forecast?api-version=2022-10-01",
    "ClientId" : "<your-client-id>",
    "ClientSecret" : "<your-client-secret>",
    "TenantId" : "<your-tenant-id>"
  }

Teremos dois serviços implementados em nosso projeto:

  • IEnterpriseIdentityService: responsável por autenticar a aplicação, e consequentemente obter o access token;
  •  ICostManagementService: serviço responsável em fazer a chamada à API https://management.azure.com.

Vamos as interfaces e implementações:

IEnterpriseIdentityService

using AzureCostManagement.Api.Models;
namespace AzureCostManagement.Api
{
    public interface IEnterpriseIdentityService
    {
        Task<AccessToken> GetAccessToken(string scope);
    }
}
using System.Text.Json;
using AzureCostManagement.Api.Models;

namespace AzureCostManagement.Api
{
    public class AzureAdService : IEnterpriseIdentityService
    {
        private readonly IConfiguration _configuration;
        private readonly HttpClient _httpClient;
        
        public AzureAdService(
        IConfiguration configuration, 
        IHttpClientFactory httpclientFactory)
        {
            _configuration = configuration;
            _httpClient = httpclientFactory.CreateClient();
        }
        public async Task<AccessToken> GetAccessToken(string scope)
        {
            var url = _configuration.GetSection("Application:AzureAdLoginUrlResource").Value!;
            
            var path = _configuration.GetSection("Application:AzureAdLoginUrlPath").Value!;

            var dict = new Dictionary<string, string>
            {
                { "grant_type", "client_credentials" },
                { "client_id", _configuration.GetSection("Application:ClientId").Value! },
                { "client_secret", _configuration.GetSection("Application:ClientSecret").Value! },
                { "scope", $"{scope}/.default" }
            };
   
            var fullUrl = string.Format(
                $"{url}{path}",
                _configuration.GetSection("Application:TenantId").Value!);

            var requestBody = new FormUrlEncodedContent(dict);
            
            var response = await _httpClient.PostAsync(fullUrl, requestBody);

            response.EnsureSuccessStatusCode();

            var result = await response.Content.ReadAsStringAsync();

            return JsonSerializer.Deserialize<AccessToken>(result)!;
        }
    }
}
using System.Text.Json.Serialization;

namespace AzureCostManagement.Api.Models
{
    public record AccessToken
    {
         [JsonPropertyName("token_type")]
        public string TokenType { get; set; } = default!;

        [JsonPropertyName("expires_in")]
        public long ExpiresIn { get; set; }

        [JsonPropertyName("access_token")]
        public string Token { get; set; } = default!;
    }
}

ICostManagementService

namespace AzureCostManagement.Api.Models
{
    public interface ICostManagementService
    {
        Task<CostManagementResult> GetUsageCostsAync(Guid subscriptionId, DateTime endDate, DateTime startDate);
    }
}
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace AzureCostManagement.Api.Models
{
    public record CostManagementResult
    {
        [JsonPropertyName("id")]
        public string Id { get; init; } = default!;

        [JsonPropertyName("name")]
        public string Name { get; init; } = default!;

        [JsonPropertyName("type")]
        public string Type { get; init; } = default!;

        [JsonPropertyName("properties")]
        public CostManagementProperties Properties { get; init; } = default!;
    }

    public record CostManagementProperties
    {
        [JsonPropertyName("nextLink")]
        public string? NextLink { get; init; }

        [JsonPropertyName("columns")]
        public IEnumerable<CostManagementColumn> Columns { get; init; } = default!;

        [JsonPropertyName("rows")]
        public IEnumerable<IEnumerable<dynamic>> Rows { get; init; } = default!;

        public IEnumerable<CostManagementRow> RowsFormated
        {
            get
            {
                var dateFormat = "yyyyMMdd";

                return Rows.Select(x => new CostManagementRow
                {
                    // Amount
                    Amount = ((JsonElement)x.ToArray<object>()[0]).GetDouble(),

                    // Date
                    Date = DateTime.ParseExact(
                        ((JsonElement)x.ToArray<object>()[1]).GetInt64().ToString(),
                        dateFormat,
                        CultureInfo.InvariantCulture),

                    // CostStatus
                    CostStatus = ((JsonElement)x.ToArray<object>()[2]).GetString()!,

                    // Currency
                    Currency = ((JsonElement)x.ToArray<object>()[3]).GetString()!,
                });
            }
        }        
    }

    public record CostManagementRow
    {
        public double Amount { get; init; }

        public DateTime Date { get; init; }

        public string CostStatus { get; init; } = default!;

        public string Currency { get; init; } = default!;
    }

    public record CostManagementColumn
    {
        [JsonPropertyName("name")]
        public string Name { get; set; } = null!;

        [JsonPropertyName("type")]
        public string Type { get; set; } = null!;
    }
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using AzureCostManagement.Api.Models;

namespace AzureCostManagement.Api
{
    public class AzureCostManagementService : ICostManagementService
    {
        private readonly IEnterpriseIdentityService _enterpriseIdentityService;
        private readonly IConfiguration _configuration;
        private readonly HttpClient _httpClient;
        
        public AzureCostManagementService(
            IEnterpriseIdentityService enterpriseIdentityService, 
            IConfiguration configuration,
            IHttpClientFactory httpClientFactory)
        {
            _enterpriseIdentityService = enterpriseIdentityService;
            _configuration = configuration;
            _httpClient = httpClientFactory.CreateClient();
        }
        
        public async Task<CostManagementResult> GetUsageCostsAync(Guid subscriptionId, DateTime endDate, DateTime startDate)
        {
            var url = _configuration.GetSection("Application:AzureCostManagementUrlResource").Value!;
            var path = _configuration.GetSection("Application:AzureCostManagementUrlpath").Value!;

            var acessToken = await _enterpriseIdentityService.GetAccessToken(url);

            var request = new CostManagementRequestBuilder()
                .WithStartDate(startDate)
                .WithEndDate(endDate)
                .Build();

            var json = JsonSerializer.Serialize(request);

            var data = new StringContent(json, Encoding.UTF8, "application/json");

            var fullUrl = string.Format(
                $"{url}{path}",
                subscriptionId);

            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", acessToken.Token);

            var response = await _httpClient.PostAsync(fullUrl, data);

            response.EnsureSuccessStatusCode();

            var result = await response.Content.ReadAsStringAsync();

            return JsonSerializer.Deserialize<CostManagementResult>(result)!;
        }
    }
}

Observe que injetamos via DI o serviço IEnterpriseIdentityService dentro da implementação AzureCostManagementService.

Com os serviços prontos, podemos implementar a classe principal da nossa aplicação!

Program.cs

Como estamos utilizando uma miniaml API, todas as configurações e métodos HTTP irão estar no mesmo arquivo. Iremos injetar via depedency injection o serviço de cost management no método GET e retornar aos usuários os valores de gastos da subscription formatados.

using AzureCostManagement.Api;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<ICostManagementService, AzureCostManagementService>();
builder.Services.AddScoped<IEnterpriseIdentityService, AzureAdService>();
builder.Services.AddHttpClient();

var app = builder.Build();

app.MapGet("/cost-management", async (
    [FromQuery] Guid subscritionId,
    ICostManagementService costManagementService) => 
{
    var today = DateTime.Today;

    var firstDayOfCurrentMonth = new DateTime(today.Year, today.Month, 1);
    var lastDayOfCurrentMonth = firstDayOfCurrentMonth.AddMonths(1).AddDays(-1);

    var data = await costManagementService.GetUsageCostsAync(
        subscritionId, 
        lastDayOfCurrentMonth, 
        firstDayOfCurrentMonth);

    return Results.Ok(new 
    {
        Values = data.Properties.RowsFormated
    });
 });

app.Run();

Podemos executar nosso projeto e fazer a chamada HTTP, passando como query string o valor da subscriptionId.

dotnet run

Temos o resultado:


Isso é tudo pessoal! Você já pode baixar o projeto por esse link, e não esquece de me seguir no LinkedIn!

Até a próxima, abraços!

💡
Podemos te ajudar com uma revisão 100% gratuita do seu ambiente cloud.
Share this post