Azure Custom Cost Management Report


In this post I want to explain an Azure Function I developed. The Function itself was developed using PowerShell Core and is therefore executable on pretty much any modern operating system. My goal was it to develop a mechanism that sends a custom Cost Management Report for a subscription to an e-mail address.

linkThe customer pain

While concepts like FinOps (https://www.finops.org/what-is-finops/) are getting more and more traction, there are is still the need of getting a detailed cost report via e-mail instead of using intelligent dashboards like PowerBi. This is why I came up with an Azure Function that tackles this problem by sending a custom cost report via e-mail.

The requirements I often here from customers are mostly one of the following:

Getting Cost Information via e-mail is more of an "Cost Control" approach rather than a "Cost Management" approach but since this is an already established an mature method we cannot ignore it. However, we can enhance the Cost Control information by:

  1. Splitting the costs in different categories like: Cost Center, Resource Group etc.
  2. Adding Cost Saving recommendations (From the Azure Advisor Service)

This makes the information more actionable and therefore, paves the way to change from a Cost Control approach towards a Cost Management approach.

linkThe solution

The solution is actually quite simple, if we do a little bit of digging and find the right query to get the needed information from the APIs. The whole solution is basically build on leveraging two APIs.

linkAzure Cost Management API

This is the tricky one. Since certain actions are not documented. But eventually I can up with this:

1<#

2# Get and Create Cost Management Report

3#>

4function Get-CostManagement($sub, $headers, $budgetname, $costCenterTag, $currency, $timeout) {

5 $costManagementBody = @"

6{

7 "properties": {

8 "currency": "$currency",

9 "dateRange": "ThisMonth",

10 "query": {

11 "type": "ActualCost",

12 "dataSet": {

13 "granularity": "Daily",

14 "aggregation": {

15 "totalCost": {

16 "name": "PreTaxCost",

17 "function": "Sum"

18 },

19 "totalCost$currency": {

20 "name": "PreTaxCost$currency",

21 "function": "Sum"

22 }

23 },

24 "sorting": [

25 {

26 "direction": "ascending",

27 "name": "UsageDate"

28 }

29 ]

30 },

31 "timeframe": "None"

32 },

33 "chart": "Area",

34 "accumulated": "true",

35 "pivots": [

36 {

37 "type": "Dimension",

38 "name": "ServiceName"

39 },

40 {

41 "type": "TagKey",

42 "name": "$costCenterTag"

43 },

44 {

45 "type": "Dimension",

46 "name": "ResourceGroupName"

47 }

48 ],

49 "scope": "subscriptions/$sub",

50 "kpis": [

51 {

52 "type": "Budget",

53 "id": "subscriptions/$sub/providers/Microsoft.Consumption/budgets/$budgetname",

54 "enabled": true

55 },

56 {

57 "type": "Forecast",

58 "enabled": false

59 }

60 ],

61 "displayName": "Spending"

62 }

63}

64"@

65 $uriCostManagement = "https://management.azure.com/subscriptions/$sub/providers/Microsoft.CostManagement/views/render?api-version=2019-11-01"

66 $response = Invoke-WebRequest -Uri $uriCostManagement -Method Post -Headers $headers -Body $costManagementBody -TimeoutSec $timeout

67 $data = $Response.Content | ConvertFrom-Json

68 $UrlDownloadpicture = $data.properties.imageUrl

69 $webclient = New-Object System.Net.WebClient

70 $imageBytes = $webClient.DownloadData($UrlDownloadpicture);

71

72 $ImageBits = [System.Convert]::ToBase64String($imageBytes)

73 ...

74}

The tricky part is to find the "/views/render" action. I did find it using the developer tools (https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide) in the new MS Edge Browser, that is based on Chromium. I found the API action and the payload needed to create a PNG of a custom cost dashboard. I simply recreated the REST call by using the Invoke-WebRequest cmdlet and "voila" it worked! :)

linkAzure Advisor API

This is more straightforward since there is a good documentation on what we need to do. So first we need to generate recommendations and then we need to get the updated/created recommendations.

To generate recommendations we use the following function:

1...

2$uri = "https://management.azure.com/subscriptions/$sub/providers/Microsoft.Advisor/generateRecommendations?api-version=2017-03-31"

3 Write-Debug ("POST {0}" -f $uri)

4 $response = Invoke-WebRequest -Uri $uri -Method Post -Headers $headers

5 $statusuri = New-Object System.Uri($response.Headers.Location)

6 Write-Debug ("GET {0}" -f $statusUri)

7 $secondsElapsed = 0

8 while ($secondsElapsed -lt $timeout) {

9 $response = Invoke-WebRequest -Uri $statusUri -Method Get -Headers $headers

10 if ($response.StatusCode -eq 204) { break }

11 Write-Host ("Waiting for generation to complete for subscription {0}..." -f $sub)

12 Start-Sleep -Seconds 1

13 $secondsElapsed++

14 }

15 $result = New-Object PSObject -Property @{"SubscriptionId" = $sub; "Status" = "Success"; "SecondsElapsed" = $secondsElapsed }

16 if ($secondsElapsed -ge $timeout) {

17 $result.Status = "Timed out"

18 }

19}

20...

The code above generates recommendations and waits until the recommendation creation is completed before continuing.

To get Advisor Recommendations we can use the following URI:

1...

2$AdvisorRecommendationsUri = "https://management.azure.com/subscriptions/$sub/providers/Microsoft.Advisor/recommendations?api-version=2017-04-19"

3 $result = "letscheck"

4 try {

5 $AdvisorRecommendationsResult = Invoke-WebRequest -Uri $AdvisorRecommendationsUri -Method Get -Headers $headers

6 }

7 catch {

8 $result = ($_.ErrorDetails.Message | ConvertFrom-Json).error.code

9 }

10...

linkThe result

If everything works out and all the prerequisites (find them in the the run.ps1 file) are met, the resulting e-mail that is sent. The e-mail should look something like this:

CostManagementFunction

linkThe deployment

It's quite straight forward:

  1. Clone the Git repository (https://github.com/lpassig/CostManagementFunction)
  2. Open VSCode with the Azure Function Extention installed (https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)
  3. Sign in to your Azure Account by clicking "Sign in to Azure..." in the Azure Functions explorer
  4. Create a new Azure Function and deploy the the content of the Github repository

That's about it :) !

linkThe alternatives

If you are more mature in you FinOps journey and want to drill into data straigh away, you can have a look at a couple of projects from my colleagues. The first one is from Helder Pinto, he blogs on the techcommunity1 regarding his Optimization Engine project on Github2.

OptimizationEngine

The second option is a project from our consulting services colleaugs that published one of their projects3 called the "Continuous Cloud Optimization Power BI Dashboards Project"

Continuous Cloud Optimization Power BI Dashboards

linkClosing

Developing this solution as well as writing this blog post was quite "quick and dirty", so if there are issues feel free to create an "Issue" in the github repository (https://github.com/lpassig/CostManagementFunction) and as always: Let me know what you think!

linkSources

1 Techcommunity blog post from Helder Pinto (https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/augmenting-azure-advisor-cost-recommendations-for-automated/ba-p/1339298)

2 Helder Pinto's Github repository (https://github.com/helderpinto/AzureOptimizationEngine)

3 Continuous Cloud Optimization Power BI Dashboards Project (https://github.com/Azure/ccodashboard)


The customer painThe solutionAzure Cost Management APIAzure Advisor APIThe resultThe deploymentThe alternativesClosingSources

Home Successful cloud adoption primer COVID-19's impact on cloud strategy! Pillars of Well-Architected Azure Workloads! Azure Custom Cost Management Report! Enable Self-Service Creation of Azure Subscriptions!