diff --git a/docs-mslearn/toolkit/alerts/configure-finops-alerts.md b/docs-mslearn/toolkit/alerts/configure-finops-alerts.md
new file mode 100644
index 000000000..09de6ad88
--- /dev/null
+++ b/docs-mslearn/toolkit/alerts/configure-finops-alerts.md
@@ -0,0 +1,96 @@
+---
+title: Configure FinOps alerts
+description: FinOps alerts will accelerate your cost optimization efforts with scheduled notifications that continuously monitor your cloud environment, empowering you to make informed decisions without the hassle.
+author: bandersmsft
+ms.author: banders
+ms.date: 02/18/2025
+ms.topic: concept-article
+ms.service: finops
+ms.subservice: finops-toolkit
+ms.reviewer: robelmamecha
+#customer intent: As a FinOps practitioner, I want to deploy FinOps alerts to detect idle resources.
+---
+
+
+# Configure FinOps alerts
+
+FinOps alerts is an automated and proactive cost optimization tool built on Azure Logic Apps. It continuously scans your Azure environment for idle resources and sends notifications to help you take timely action. This solution empowers FinOps practitioners to better manage cloud spending while minimizing waste in the environment.
+
+
+
+## Overview
+
+To configure FinOps alerts, follow these steps:
+
+1. **Deploy FinOps alerts**
+
+ > [!div class="nextstepaction"]
+ > [Deploy to Azure](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fmicrosoft.github.io%2Ffinops-toolkit%2Fdeploy%2Ffinops-alerts-latest.json/createUIDefinitionUri/https%3A%2F%2Fmicrosoft.github.io%2Ffinops-toolkit%2Fdeploy%2Ffinops-alerts-latest.ui.json)
+ >
+ > [!div class="nextstepaction"]
+ > [Deploy to Azure Gov](https://portal.azure.us/#create/Microsoft.Template/uri/https%3A%2F%2Fmicrosoft.github.io%2Ffinops-toolkit%2Fdeploy%2Ffinops-alerts-latest.json/createUIDefinitionUri/https%3A%2F%2Fmicrosoft.github.io%2Ffinops-toolkit%2Fdeploy%2Ffinops-alerts-latest.ui.json)
+ >
+ > [!div class="nextstepaction"]
+ > [Deploy to Azure China](https://portal.azure.cn/#create/Microsoft.Template/uri/https%3A%2F%2Fmicrosoft.github.io%2Ffinops-toolkit%2Fdeploy%2Ffinops-alerts-latest.json/createUIDefinitionUri/https%3A%2F%2Fmicrosoft.github.io%2Ffinops-toolkit%2Fdeploy%2Ffinops-alerts-latest.ui.json)
+
+2. **Authorize API connection**
+
+ > [!NOTE]
+ > After deployment, the Logic app will show a failed run this is due to the API connection, this is a temporary state until authorization is complete.
+
+ 1. Select the **API connection** resource, then select **Edit API Connection** in the General tab to authorize the connection. Once you enable connection select **Save**.
+
+ :::image type="content" source="./media/finops-alerts/authorize-api-connection.png" alt-text="Screenshot of editing API connection." lightbox="./media/finops-alerts/authorize-api-connection.png" :::
+
+3. **Assigning reader permission**
+
+ 1. The Logic App’s system-assigned identity must have the **Reader** role on the targeted subscriptions. This role enables it to query resource utilization data. Follow [these steps](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal-managed-identity#system-assigned-managed-identity) to assign the reader role.
+
+ 1. For environments that span multiple subscriptions, consider assigning the Reader role at the management group level to streamline permissions management and ensure comprehensive monitoring.
+
+4. **Configuring the Logic App**
+
+ 1. Within the Logic App designer adjust the recurrence setting (defaulting to 1 week) based on your monitoring needs.
+
+ 2. Configure details such as the email subject, alert recipients, and filter which subscription IDs should be included or excluded. This level of customization allows you to tailor the monitoring to your specific cloud environment and cost optimization strategy.
+
+ 3. If needed, further modify the Logic App’s workflow such as conditions, thresholds, and notification channels to align with your organization’s requirements.
+
+5. **Testing and validation**
+
+ 1. After completing the setup, run the Logic App to ensure it correctly identifies idle resources and triggers the appropriate notifications.
+
+ 2. Analyze test results to adjust thresholds or alert parameters as necessary.
+
+
+
+## Give feedback
+
+Let us know how we're doing with a quick review. We use these reviews to improve and expand FinOps tools and resources.
+
+> [!div class="nextstepaction"]
+> [Give feedback](https://portal.azure.com/#view/HubsExtension/InProductFeedbackBlade/extensionName/FinOpsToolkit/cesQuestion/How%20easy%20or%20hard%20is%20it%20to%20use%20FinOps%20alerts%3F/cvaQuestion/How%20valuable%20are%20FinOps%20alerts%3F/surveyId/FTK0.8/bladeName/Alerts/featureName/Overview)
+
+If you're looking for something specific, vote for an existing or create a new idea. Share ideas with others to get more votes. We focus on ideas with the most votes.
+
+> [!div class="nextstepaction"]
+> [Vote on or suggest ideas](https://github.com/microsoft/finops-toolkit/issues?q=is%3Aissue%20is%3Aopen%20label%3A%22Tool%3A%20FinOps%20alerts%22%20sort%3Areactions-%2B1-desc)
+
+
+
+## Related content
+
+Related FinOps capabilities:
+
+- [Reporting and analytics](../../framework/understand/reporting.md)
+- [Workload optimization](../../framework/optimize/workloads.md)
+
+Related products:
+
+- [Azure Logic Apps](/azure/azure-logic-apps/)
+
+Related solutions:
+
+- [FinOps workbooks](../workbooks/finops-workbooks-overview.md)
+
+
diff --git a/docs-mslearn/toolkit/alerts/finops-alerts-overview.md b/docs-mslearn/toolkit/alerts/finops-alerts-overview.md
new file mode 100644
index 000000000..cdfe8daee
--- /dev/null
+++ b/docs-mslearn/toolkit/alerts/finops-alerts-overview.md
@@ -0,0 +1,75 @@
+---
+title: FinOps alerts overview
+description: FinOps alerts will accelerate your cost optimization efforts with scheduled notifications that continuously monitor your cloud environment, empowering you to make informed decisions without the hassle.
+author: bandersmsft
+ms.author: banders
+ms.date: 02/18/2025
+ms.topic: concept-article
+ms.service: finops
+ms.subservice: finops-toolkit
+ms.reviewer: robelmamecha
+#customer intent: As a FinOps practitioner, I need to learn about FinOps alerts.
+---
+
+
+# FinOps alerts
+
+FinOps alerts is an automated and proactive cost optimization tool built on Azure Logic Apps. It continuously scans your Azure environment for idle resources and sends notifications to help you take timely action. This solution empowers FinOps practitioners to better manage cloud spending while minimizing waste in the environment.
+
+## How it works
+
+FinOps alerts leverages Azure Logic Apps to automate detection of waste across selected subscriptions:
+
+- **Automated resource monitoring**
FinOps alerts runs on a configurable schedule to assess resource activity. It inspects various resource properties to identify idle resources that might be leading to unnecessary costs.
+
+- **Automated notifications**
Upon detecting idle resources, the Logic App triggers notifications—via email or other integrated channels to designated administrators, ensuring that the right stakeholders are alerted promptly to review and to take action.
+
+- **Flexibility**
Users can tailor key parameters, including the recurrence interval, alert recipients, and the specific subscriptions to monitor. This makes the tool adaptable to a wide range of cloud environments.
+
+## Benefits
+
+- By automating the detection of idle resources, FinOps alerts helps you preemptively address inefficient spending, ensuring that cloud costs remain under control.
+
+- Designed to operate seamlessly across single or multi-subscription environments.
+
+## Why FinOps alerts?
+
+If you are using the [FinOps workbook](/finops/toolkit/workbooks/finops-workbooks-overview) to identify idle or underutilized resources, you'll notice it doesn’t provide any automatic alerts-meaning engineers must continually check back to review flagged items. FinOps alerts automates this process, ensuring that when resources are identified as potentially inefficient, stakeholders receive timely notifications without having to manually monitor a workbook. This not only frees up valuable time for busy teams but also improves the chances of catching cost-saving opportunities as they arise. Moreover, future releases of the app are planned to include additional queries, broadening its scope and further enhancing its ability to deliver actionable insights for sustainable cloud cost management.
+
+## Required permissions
+
+Deploying FinOps alerts template requires one of the following:
+
+- For least-privileged access, [Contributor](/azure/role-based-access-control/built-in-roles#contributor) and [Role Based Access Control Administrator](/azure/role-based-access-control/built-in-roles#role-based-access-control-administrator)
+- [Owner](/azure/role-based-access-control/built-in-roles#owner)
+
+## Give feedback
+
+Let us know how we're doing with a quick review. We use these reviews to improve and expand FinOps tools and resources.
+
+> [!div class="nextstepaction"]
+> [Give feedback](https://portal.azure.com/#view/HubsExtension/InProductFeedbackBlade/extensionName/FinOpsToolkit/cesQuestion/How%20easy%20or%20hard%20is%20it%20to%20use%20FinOps%20alerts%3F/cvaQuestion/How%20valuable%20are%20FinOps%20alerts%3F/surveyId/FTK0.8/bladeName/Alerts/featureName/Overview)
+
+If you're looking for something specific, vote for an existing or create a new idea. Share ideas with others to get more votes. We focus on ideas with the most votes.
+
+> [!div class="nextstepaction"]
+> [Vote on or suggest ideas](https://github.com/microsoft/finops-toolkit/issues?q=is%3Aissue%20is%3Aopen%20label%3A%22Tool%3A%20FinOps%20alerts%22%20sort%3Areactions-%2B1-desc)
+
+
+
+## Related content
+
+Related FinOps capabilities:
+
+- [Reporting and analytics](../../framework/understand/reporting.md)
+- [Workload optimization](../../framework/optimize/workloads.md)
+
+Related products:
+
+- [Azure Logic Apps](/azure/azure-logic-apps/)
+
+Related solutions:
+
+- [FinOps workbooks](../workbooks/finops-workbooks-overview.md)
+
+
diff --git a/docs-mslearn/toolkit/alerts/media/configure-finops-alerts/authorize-api-connection.png b/docs-mslearn/toolkit/alerts/media/configure-finops-alerts/authorize-api-connection.png
new file mode 100644
index 000000000..4d13e5459
Binary files /dev/null and b/docs-mslearn/toolkit/alerts/media/configure-finops-alerts/authorize-api-connection.png differ
diff --git a/docs-mslearn/toolkit/changelog.md b/docs-mslearn/toolkit/changelog.md
index 93836fcc3..b49fbef99 100644
--- a/docs-mslearn/toolkit/changelog.md
+++ b/docs-mslearn/toolkit/changelog.md
@@ -52,6 +52,7 @@ _Released March 2025_
- If you rely on this setting, please [create an issue in GitHub](https://aka.ms/ftk/ideas) and let us know what you need.
- **Fixed**
- Fixed the "The import Storage URL matches no exports" error ([#1344](https://github.com/microsoft/finops-toolkit/issues/1344)).
+ - Added resource-specific tags to the stop all triggers deployment script ([#1330](https://github.com/microsoft/finops-toolkit/issues/1330))
**[Rate optimization](power-bi/rate-optimization.md)**
@@ -78,6 +79,13 @@ _Released March 2025_
- Please do not delete the managed identities. Deleting managed identities can result in errors during upgrades.
- Removed the trusted external tenants setting due to an error causing redeployments to fail. Please enable this after deploying FinOps hubs the first time.
+### [FinOps alerts](finops-alerts-overview.md) v0.9
+
+- **Added**
+ - Overview documentation on how the FinOps alert tool works in the [FinOps alerts overview](./alerts/finops-alerts-overview.md).
+ - Configuration steps in the [Configure FinOps alerts](./alerts/configure-finops-alerts.md).
+ - FinOps alerts deployment template.
+
> [!div class="nextstepaction"]
> [Download](https://github.com/microsoft/finops-toolkit/releases/tag/v0.9)
> [!div class="nextstepaction"]
diff --git a/docs/finops-alerts.md b/docs/finops-alerts.md
new file mode 100644
index 000000000..ab9a1fc0e
--- /dev/null
+++ b/docs/finops-alerts.md
@@ -0,0 +1,66 @@
+---
+layout: default
+title: FinOps alerts
+browser: FinOps alerts - Monitor your Azure environment for optimization
+nav_order: 35
+description: 'Accelerate your cost optimization efforts with scheduled notifications that continuously monitor your cloud environment, empowering you to make informed decisions without the hassle.'
+permalink: /alerts
+#customer intent: As a Finops practitioner, I need to learn about FinOps Alerts
+---
+
+FinOps alerts
+Accelerate your cost optimization efforts with scheduled notifications that continuously monitor your cloud environment, empowering you to make informed decisions without the hassle.
+{: .fs-6 .fw-300 }
+
+Deploy
+Documentation
+
+---
+
+Designed with flexibility in mind, FinOps alerts run on a fully configurable schedule, continuously scanning your selected subscriptions for idle resources.
+
+
+
What's new in March 2025v0.9
+
+ March released FinOps alerts to boost cost optimization with automated notifications that monitor your cloud.
+
+
See all changes
+
+
+
+## Actionable insights for smarter cloud management
+
+
+
+
🔍 Monitor idle resources
+
Identify idle resources across your environment to uncover cost savings.
+
+
+
📧 Automated notifications
+
Get notified when idle resources are detected and take control of your environment.
+
+
+
🔔 Customize alerts
+
Tune alerts to reach key stakeholders, take new actions, and more!
+
+
+
📃 Review resource utilization
+
Track resource inventory and utilization of targeted services.
+
+
+
+
+## Deploy FinOps alerts
+
+FinOps alerts works best with the Owner role. For least privileged access, use the Contributor and Role Based Access Control Administrator roles.
+
+Deploy to Azure
+Deploy to Azure Gov
+Deploy to Azure China
+
+💜 Give feedback
+
+
+
diff --git a/src/templates/finops-alerts/createUiDefinition.json b/src/templates/finops-alerts/createUiDefinition.json
new file mode 100644
index 000000000..7f519e102
--- /dev/null
+++ b/src/templates/finops-alerts/createUiDefinition.json
@@ -0,0 +1,72 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#",
+ "handler": "Microsoft.Azure.CreateUIDef",
+ "version": "0.1.2-preview",
+ "parameters": {
+ "config": {
+ "basics": {
+ "description": "FinOps alerts is an Azure Logic Apps-based automated detection system that identifies idle resources across selected subscriptions on a configurable schedule and sends notifications to admins to investigate and take action",
+ "location": {
+ "label": "Location",
+ "toolTip": "Location of logic app must be in the same region as the resource group.",
+ "resourceTypes": [
+ "Microsoft.Logic/workflows",
+ "Microsoft.Web/connections"
+ ]
+ }
+ }
+ },
+ "basics": [
+ {
+ "name": "finopsalertsName",
+ "type": "Microsoft.Common.TextBox",
+ "label": "FinOps alerts name",
+ "defaultValue": "finops-alerts",
+ "toolTip": "Name of the FinOps alerts instance.",
+ "constraints": {
+ "required": true,
+ "regex": "^[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$",
+ "validationMessage": "Name must be between 3 and 63 characters long and can contain only lowercase letters, numbers, and hyphens. The first and last characters in the name must be alphanumeric."
+ },
+ "visible": true
+ },
+ {
+ "name": "connectionsName",
+ "type": "Microsoft.Common.TextBox",
+ "label": "Connection name",
+ "defaultValue": "finops-alerts-connection",
+ "toolTip": "Name of the API connection.",
+ "constraints": {
+ "required": true,
+ "regex": "^[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$",
+ "validationMessage": "Name must be between 3 and 63 characters long and can contain only lowercase letters, numbers, and hyphens. The first and last characters in the name must be alphanumeric."
+ },
+ "visible": true
+ }
+ ],
+ "steps": [{
+ "name": "tags",
+ "label": "Tags",
+ "elements": [
+ {
+ "name": "tagsByResource",
+ "label": "Tags",
+ "toolTip": "Tags to apply to resources.",
+ "type": "Microsoft.Common.TagsByResource",
+ "resources": [
+ "Microsoft.Logic/workflows",
+ "Microsoft.Web/connections"
+ ]
+ }
+ ]
+ }],
+ "outputs": {
+ "hubName": "[basics('finopsalertsName')]",
+ "location": "[location()]",
+ "tagsByResource": "[steps('tags').tagsByResource]"},
+ "resourceTypes": [
+ "Microsoft.Logic/workflows",
+ "Microsoft.Web/connections"
+ ]
+ }
+}
diff --git a/src/templates/finops-alerts/main.bicep b/src/templates/finops-alerts/main.bicep
new file mode 100644
index 000000000..185e8eaaf
--- /dev/null
+++ b/src/templates/finops-alerts/main.bicep
@@ -0,0 +1,51 @@
+//==============================================================================
+// Parameters
+//==============================================================================
+
+targetScope = 'resourceGroup'
+
+@description('Azure location where resources should be created')
+param location string = resourceGroup().location
+
+@description('Name of the logic app')
+@minLength(1)
+@maxLength(20)
+param appName string = 'finops-alerts'
+
+@description('Specifies the frequency of the recurrence trigger. Possible values are Week, Day or Hour.')
+param recurrenceFrequency string = 'Week'
+
+@description('Specifies the interval for the recurrence trigger. Represents the number of frequency units.')
+param recurrenceInterval int = 1
+
+@description('Specifies the type of the trigger. For this example, it is a recurrence trigger.')
+param recurrenceType string = 'Recurrence'
+
+//@description('A list of resource Id of subscriptions.')
+//param subscriptionsIds array = [
+ //'dbc1e833-6b33-4788-b219-b9266b898fad'
+//]
+
+@description('The Id of the subscription to deploy the logic app in.')
+param logicAppSubscriptionId string = ''
+
+@description('The name of the resource group.')
+param resourceGroupName string = ''
+
+//==============================================================================
+// Resources
+//==============================================================================
+
+module logicApp 'modules/logicApp.bicep' = {
+ name: 'logicApp-${uniqueString(deployment().name,location,appName)}'
+ scope: resourceGroup(logicAppSubscriptionId, resourceGroupName)
+ params: {
+ appName: appName
+ location: location
+ recurrenceFrequency: recurrenceFrequency
+ recurrenceInterval: recurrenceInterval
+ recurrenceType: recurrenceType
+ }
+}
+
+
diff --git a/src/templates/finops-alerts/modules/logicApp.bicep b/src/templates/finops-alerts/modules/logicApp.bicep
new file mode 100644
index 000000000..3d849a668
--- /dev/null
+++ b/src/templates/finops-alerts/modules/logicApp.bicep
@@ -0,0 +1,4218 @@
+//==============================================================================
+// Parameters
+//==============================================================================
+@description('Azure location where resources should be created')
+param location string = resourceGroup().location
+
+@description('Name of the logic app')
+@minLength(1)
+@maxLength(20)
+param appName string = 'finops-alerts'
+
+@description('Specifies the frequency of the recurrence trigger. Possible values are Week, Day or Hour.')
+param recurrenceFrequency string = 'Week'
+
+@description('Specifies the interval for the recurrence trigger. Represents the number of frequency units.')
+param recurrenceInterval int = 1
+
+@description('Specifies the type of the trigger. For this example, it is a recurrence trigger.')
+param recurrenceType string = 'Recurrence'
+
+//==============================================================================
+// Variables
+//==============================================================================
+var safeSuffix = replace(replace(toLower(appName), '-', ''), '_', '')
+
+
+@description('Name of the connection to use for the logic app')
+var connectionName = '${safeSuffix}-connection'
+
+@description('Display name of the connection')
+var displayName = '${safeSuffix}-connection'
+
+//Only one actionKey will be needed here after updating the code
+@description('Used to track number of functions available in this App')
+var actionKeys = [
+ 'Send_an_email_V2'
+]
+
+//==============================================================================
+// Resources
+//==============================================================================
+
+resource finopsAlerts 'Microsoft.Logic/workflows@2019-05-01' = {
+ identity: {
+ type: 'SystemAssigned'
+ }
+ properties: {
+ state: 'Enabled'
+ definition: {
+ '$schema': 'https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#'
+ contentVersion: '1.0.0.0'
+ parameters: {
+ '$connections': {
+ defaultValue: {}
+ type: 'Object'
+ }
+ }
+ triggers: {
+ Recurrence: {
+ recurrence: {
+ frequency: recurrenceFrequency
+ interval: recurrenceInterval
+ }
+ evaluatedRecurrence: {
+ frequency: recurrenceFrequency
+ interval: recurrenceInterval
+ }
+ type: recurrenceType
+ }
+ }
+ actions: {
+ For_each_App_GW: {
+ foreach: '@body(\'Parse_idle_App_Gateways\')?[\'data\']'
+ actions: {
+ Set_App_Gateways_URI: {
+ type: 'SetVariable'
+ inputs: {
+ name: 'AppGwURI'
+ value: '@{concat(\'https://portal.azure.com/#@\',items(\'For_each_App_GW\')?[\'tenantId\'],\'/resource\',items(\'For_each_App_GW\')?[\'id\'])}'
+ }
+ }
+ Compose_AppGw: {
+ runAfter: {
+ Set_App_Gateways_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Compose'
+ inputs: '@variables(\'AppGwURI\')'
+ }
+ Append_to_App_Gateway_HTML: {
+ runAfter: {
+ Compose_AppGw: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'AppGatewayHTML'
+ value: ' \n | @{items(\'For_each_App_GW\')?[\'name\']} | \n @{items(\'For_each_App_GW\')?[\'resourceGroup\']} | \n @{items(\'For_each_App_GW\')?[\'subscriptionName\']} | \n
'
+ }
+ }
+ }
+ runAfter: {
+ Condition_App_Gateway: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Foreach'
+ runtimeConfiguration: {
+ concurrency: {
+ repetitions: 1
+ }
+ }
+ }
+ For_each_Disk: {
+ foreach: '@body(\'Parse_Idle_disks\')?[\'data\']'
+ actions: {
+ Set_Disk_URI: {
+ type: 'SetVariable'
+ inputs: {
+ name: 'DiskURI'
+ value: '@{concat(\'https://portal.azure.com/#@\',items(\'For_each_Disk\')?[\'tenantId\'],\'/resource\',items(\'For_each_Disk\')?[\'DiskId\'])}'
+ }
+ }
+ Compose_Disk: {
+ runAfter: {
+ Set_Disk_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Compose'
+ inputs: '@variables(\'DiskURI\')'
+ }
+ Append_to_Idle_Disk_HTML: {
+ runAfter: {
+ Compose_Disk: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'IdleDiskHTML'
+ value: ' \n | @{items(\'For_each_Disk\')?[\'DiskName\']} | \n @{items(\'For_each_Disk\')?[\'resourceGroup\']} | \n @{items(\'For_each_Disk\')?[\'subscriptionName\']} | \n
'
+ }
+ }
+ }
+ runAfter: {
+ Condition_Disk: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Foreach'
+ runtimeConfiguration: {
+ concurrency: {
+ repetitions: 1
+ }
+ }
+ }
+ For_each_IP_address: {
+ foreach: '@body(\'Parse_Idle_IP_addresses\')?[\'data\']'
+ actions: {
+ Set_IP_address_URI: {
+ type: 'SetVariable'
+ inputs: {
+ name: 'IPAddressURI'
+ value: '@{concat(\'https://portal.azure.com/#@\',items(\'For_each_IP_address\')?[\'tenantId\'],\'/resource\',items(\'For_each_IP_address\')?[\'PublicIpId\'])}'
+ }
+ }
+ Compose_IP: {
+ runAfter: {
+ Set_IP_address_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Compose'
+ inputs: '@variables(\'IPAddressURI\')'
+ }
+ Append_to_IP_Address_HTML: {
+ runAfter: {
+ Compose_IP: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'IPAddressHTML'
+ value: ' \n | @{items(\'For_each_IP_address\')?[\'IPName\']} | \n @{items(\'For_each_IP_address\')?[\'resourceGroup\']} | \n @{items(\'For_each_IP_address\')?[\'subscriptionName\']} | \n
'
+ }
+ }
+ }
+ runAfter: {
+ Condition_IP_Address: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Foreach'
+ runtimeConfiguration: {
+ concurrency: {
+ repetitions: 1
+ }
+ }
+ }
+ For_each_Load_Balancer: {
+ foreach: '@body(\'Parse_Idle_Load_Balancers\')?[\'data\']'
+ actions: {
+ Set_Load_Balancer_URI: {
+ type: 'SetVariable'
+ inputs: {
+ name: 'LoadBalancerURI'
+ value: '@{concat(\'https://portal.azure.com/#@\',items(\'For_each_Load_Balancer\')?[\'tenantId\'],\'/resource\',items(\'For_each_Load_Balancer\')?[\'loadBalancerid\'])}'
+ }
+ }
+ Compose_LB: {
+ runAfter: {
+ Set_Load_Balancer_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Compose'
+ inputs: '@variables(\'LoadBalancerURI\')'
+ }
+ Append_to_Load_Balancer_HTML: {
+ runAfter: {
+ Compose_LB: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'LoadBalancerHTML'
+ value: ' \n | @{items(\'For_each_Load_Balancer\')?[\'LoadBalancerName\']} | \n @{items(\'For_each_Load_Balancer\')?[\'resourceGroup\']} | \n @{items(\'For_each_Load_Balancer\')?[\'subscriptionName\']} | \n
'
+ }
+ }
+ }
+ runAfter: {
+ Condition_Load_Balancer: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Foreach'
+ runtimeConfiguration: {
+ concurrency: {
+ repetitions: 1
+ }
+ }
+ }
+ For_each_Snapshot: {
+ foreach: '@body(\'Parse_Snapshots\')?[\'data\']'
+ actions: {
+ Set_Snapshot_URI: {
+ type: 'SetVariable'
+ inputs: {
+ name: 'SnapshotURI'
+ value: '@{concat(\'https://portal.azure.com/#@\',items(\'For_each_Snapshot\')?[\'tenantId\'],\'/resource\',items(\'For_each_Snapshot\')?[\'SnapshotId\'])}\n'
+ }
+ }
+ Compose_Snapshot: {
+ runAfter: {
+ Set_Snapshot_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Compose'
+ inputs: '@variables(\'SnapshotURI\')'
+ }
+ Append_to_Disk_Snapshot_HTML: {
+ runAfter: {
+ Compose_Snapshot: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'DiskSnapshotHTML'
+ value: ' \n | @{items(\'For_each_Snapshot\')?[\'Snapshotname\']} | \n @{items(\'For_each_Snapshot\')?[\'resourceGroup\']} | \n @{items(\'For_each_Snapshot\')?[\'subscriptionName\']} | \n
'
+ }
+ }
+ }
+ runAfter: {
+ Condition_Disk_Snapshots: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Foreach'
+ runtimeConfiguration: {
+ concurrency: {
+ repetitions: 1
+ }
+ }
+ }
+ For_each_Stopped_VM: {
+ foreach: '@body(\'Parse_stopped_VMs\')?[\'data\']'
+ actions: {
+ Set_Stopped_VM_URI: {
+ type: 'SetVariable'
+ inputs: {
+ name: 'StoppedVMURI'
+ value: '@{concat(\'https://portal.azure.com/#@\',items(\'For_each_Stopped_VM\')?[\'tenantId\'],\'/resource\',items(\'For_each_Stopped_VM\')?[\'VirtualMachineId\'])}'
+ }
+ }
+ Compose_VM: {
+ runAfter: {
+ Set_Stopped_VM_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Compose'
+ inputs: '@variables(\'StoppedVMURI\')'
+ }
+ Append_to_Stopped_VM_HTML: {
+ runAfter: {
+ Compose_VM: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'StoppedVMHTML'
+ value: ' \n | @{items(\'For_each_Stopped_VM\')?[\'VMname\']} | \n @{items(\'For_each_Stopped_VM\')?[\'resourceGroup\']} | \n @{items(\'For_each_Stopped_VM\')?[\'subscriptionName\']} | \n
'
+ }
+ }
+ }
+ runAfter: {
+ Condition_Stopped_VMs: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Foreach'
+ runtimeConfiguration: {
+ concurrency: {
+ repetitions: 1
+ }
+ }
+ }
+ Get_idle_App_Gateways: {
+ runAfter: {
+ Initialize_resources_table: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Http'
+ inputs: {
+ uri: 'https://management.azure.com//providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01'
+ method: 'POST'
+ body: {
+ query: '@{variables(\'resourcesTable\')} | where type =~ \'Microsoft.Network/applicationGateways\'| extend backendPoolsCount = array_length(properties.backendAddressPools),SKUName= tostring(properties.sku.name), SKUTier=tostring(properties.sku.tier),SKUCapacity=properties.sku.capacity,backendPools=properties.backendAddressPools| join (resources | where type =~ \'Microsoft.Network/applicationGateways\'| mvexpand backendPools = properties.backendAddressPools| extend backendIPCount =array_length(backendPools.properties.backendIPConfigurations) | extend backendAddressesCount = array_length(backendPools.properties.backendAddresses) | extend backendPoolName=backendPools.properties.backendAddressPools.name | summarize backendIPCount = sum(backendIPCount) ,backendAddressesCount=sum(backendAddressesCount) by id) on id| project-away id1| where (backendIPCount == 0 or isempty(backendIPCount)) and (backendAddressesCount==0 or isempty(backendAddressesCount))| order by id asc | join kind=leftouter ( resourcecontainers | where type == \'microsoft.resources/subscriptions\' | project subscriptionId, subscriptionName = name) on subscriptionId'
+ scope: 'Tenant'
+ }
+ authentication: {
+ type: 'ManagedServiceIdentity'
+ }
+ }
+ }
+ Get_idle_Disks: {
+ runAfter: {
+ Initialize_resources_table: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Http'
+ inputs: {
+ uri: 'https://management.azure.com//providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01'
+ method: 'POST'
+ body: {
+ query: '@{variables(\'resourcesTable\')} | where type =~ \'microsoft.compute/disks\' and managedBy == \'\'| extend diskState = tostring(properties.diskState) | where (managedBy == \'\' and diskState != \'ActiveSAS\') or (diskState == \'Unattached\' and diskState != \'ActiveSAS\') | extend DiskId = id, DiskName = name, SKUName = sku.name, SKUTier = sku.tier, DiskSizeGB = tostring(properties.diskSizeGB), Location = location, TimeCreated = tostring(properties.timeCreated) | order by DiskId asc | project DiskId, DiskName, DiskSizeGB, SKUName, SKUTier, resourceGroup, Location, TimeCreated, subscriptionId | join kind=leftouter (resourcecontainers | where type == \'microsoft.resources/subscriptions\' | project subscriptionId, subscriptionName = name) on subscriptionId'
+ scope: 'Tenant'
+ }
+ authentication: {
+ type: 'ManagedServiceIdentity'
+ }
+ }
+ }
+ Get_idle_IP_addresses: {
+ runAfter: {
+ Initialize_resources_table: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Http'
+ inputs: {
+ uri: 'https://management.azure.com//providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01'
+ method: 'POST'
+ body: {
+ query: '@{variables(\'resourcesTable\')} | where type =~ \'Microsoft.Network/publicIPAddresses\' and isempty(properties.ipConfiguration) and isempty(properties.natGateway) | extend PublicIpId=id, IPName=name, AllocationMethod=tostring(properties.publicIPAllocationMethod), SKUName=sku.name, Location=location | project PublicIpId,IPName, SKUName, resourceGroup, Location, AllocationMethod, subscriptionId, tenantId | union ( Resources | where type =~ \'microsoft.network/networkinterfaces\' and isempty(properties.virtualMachine) and isnull(properties.privateEndpoint) and isnotempty(properties.ipConfigurations) | extend IPconfig = properties.ipConfigurations | mv-expand IPconfig | extend PublicIpId= tostring(IPconfig.properties.publicIPAddress.id) | project PublicIpId | join ( resources | where type =~ \'Microsoft.Network/publicIPAddresses\' | extend PublicIpId=id, IPName=name, AllocationMethod=tostring(properties.publicIPAllocationMethod), SKUName=sku.name, resourceGroup, Location=location ) on PublicIpId | project PublicIpId,IPName, SKUName, resourceGroup, Location, AllocationMethod, subscriptionId, tenantId) | join kind=leftouter ( resourcecontainers | where type == \'microsoft.resources/subscriptions\' | project subscriptionId, subscriptionName = name) on subscriptionId'
+ scope: 'Tenant'
+ }
+ authentication: {
+ type: 'ManagedServiceIdentity'
+ }
+ }
+ }
+ Get_idle_Load_Balancers: {
+ runAfter: {
+ Initialize_resources_table: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Http'
+ inputs: {
+ uri: 'https://management.azure.com//providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01'
+ method: 'POST'
+ body: {
+ query: '@{variables(\'resourcesTable\')} | where type =~ \'microsoft.network/loadbalancers\' and tostring(properties.backendAddressPools) == \'[]\' | extend loadBalancerId=id,LBRG=resourceGroup, LoadBalancerName=name, SKU=sku, LBLocation=location | order by loadBalancerId asc | project loadBalancerId,LoadBalancerName, SKU.name,SKU.tier, LBLocation, resourceGroup, subscriptionId | join kind=leftouter ( resourcecontainers | where type == \'microsoft.resources/subscriptions\' | project subscriptionId, subscriptionName = name) on subscriptionId'
+ scope: 'Tenant'
+ }
+ authentication: {
+ type: 'ManagedServiceIdentity'
+ }
+ }
+ }
+ Get_Disk_Snapshots_older_than_30_days: {
+ runAfter: {
+ Initialize_resources_table: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Http'
+ inputs: {
+ uri: 'https://management.azure.com//providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01'
+ method: 'POST'
+ body: {
+ query: '@{variables(\'resourcesTable\')} | where type =~ \'microsoft.compute/snapshots\' | extend TimeCreated = properties.timeCreated | where TimeCreated < ago(30d) | extend SnapshotId=id, Snapshotname=name | order by id asc | project id, SnapshotId, Snapshotname, resourceGroup, location, TimeCreated ,subscriptionId | join kind=leftouter ( resourcecontainers | where type == \'microsoft.resources/subscriptions\' | project subscriptionId, subscriptionName = name) on subscriptionId'
+ scope: 'Tenant'
+ }
+ authentication: {
+ type: 'ManagedServiceIdentity'
+ }
+ }
+ }
+ Get_Stopped_VMs: {
+ runAfter: {
+ Initialize_resources_table: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Http'
+ inputs: {
+ uri: 'https://management.azure.com//providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01'
+ method: 'POST'
+ body: {
+ query: '@{variables(\'resourcesTable\')} | where type =~ \'microsoft.compute/virtualmachines\' and tostring(properties.extended.instanceView.powerState.displayStatus) != \'VM deallocated\' and tostring(properties.extended.instanceView.powerState.displayStatus) != \'VM running\'| extend VMname=name, PowerState=tostring(properties.extended.instanceView.powerState.displayStatus), VMLocation=location, VirtualMachineId=id| order by VirtualMachineId asc| project VirtualMachineId,VMname, PowerState, VMLocation, resourceGroup, subscriptionId | join kind=leftouter ( resourcecontainers | where type == \'microsoft.resources/subscriptions\' | project subscriptionId, subscriptionName = name) on subscriptionId'
+ scope: 'Tenant'
+ }
+ authentication: {
+ type: 'ManagedServiceIdentity'
+ }
+ }
+ }
+ Initialize_App_Gateways_URI: {
+ runAfter: {
+ Parse_idle_App_Gateways: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'AppGwURI'
+ type: 'string'
+ }
+ ]
+ }
+ }
+ Initialize_Disk_URI: {
+ runAfter: {
+ Parse_Idle_disks: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'DiskURI'
+ type: 'string'
+ }
+ ]
+ }
+ }
+ Initialize_IP_addresses_URI: {
+ runAfter: {
+ Parse_Idle_IP_addresses: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'IPAddressURI'
+ type: 'string'
+ }
+ ]
+ }
+ }
+ Initialize_Load_Balancer_URI: {
+ runAfter: {
+ Parse_Idle_Load_Balancers: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'LoadBalancerURI'
+ type: 'string'
+ }
+ ]
+ }
+ }
+ Initialize_Snapshot_URI: {
+ runAfter: {
+ Parse_Snapshots: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'SnapshotURI'
+ type: 'string'
+ }
+ ]
+ }
+ }
+ Initialize_Stopped_VM_URI: {
+ runAfter: {
+ Parse_stopped_VMs: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'StoppedVMURI'
+ type: 'string'
+ }
+ ]
+ }
+ }
+ Excluded_subscriptions: {
+ runAfter: {
+ Included_subscriptions: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'ExcludedSubscriptions'
+ type: 'array'
+ value: []
+ }
+ ]
+ }
+ }
+ Parse_idle_App_Gateways: {
+ runAfter: {
+ Get_idle_App_Gateways: [
+ 'Succeeded'
+ ]
+ }
+ type: 'ParseJson'
+ inputs: {
+ content: '@body(\'Get_idle_App_Gateways\')'
+ schema: {
+ properties: {
+ properties: {
+ properties: {
+ count: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ data: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ SKUCapacity: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ SKUName: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ SKUTier: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ backendAddressesCount: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ backendIPCount: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ backendPools: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ etag: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ backendAddresses: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ requestRoutingRules: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ backendPoolsCount: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ extendedLocation: {
+ properties: {}
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ identity: {
+ properties: {}
+ type: 'object'
+ }
+ kind: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ location: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ managedBy: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ plan: {
+ properties: {}
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ authenticationCertificates: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ backendAddressPools: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ etag: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ backendAddresses: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ requestRoutingRules: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ backendHttpSettingsCollection: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ etag: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ cookieBasedAffinity: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ pickHostNameFromBackendAddress: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ port: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ protocol: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ requestRoutingRules: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ requestTimeout: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ enableHttp2: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ frontendIPConfigurations: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ etag: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ httpListeners: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ privateIPAllocationMethod: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ subnet: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ frontendPorts: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ etag: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ httpListeners: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ port: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ gatewayIPConfigurations: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ etag: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ subnet: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ httpListeners: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ etag: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ frontendIPConfiguration: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ frontendPort: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ protocol: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ requestRoutingRules: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ requireServerNameIndication: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ operationalState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ probes: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ redirectConfigurations: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ requestRoutingRules: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ etag: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ backendAddressPool: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ backendHttpSettings: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ httpListener: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ ruleType: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ resourceGuid: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ rewriteRuleSets: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ sku: {
+ properties: {
+ properties: {
+ properties: {
+ capacity: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ tier: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ sslCertificates: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ urlPathMaps: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ resourceGroup: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ sku: {
+ properties: {}
+ type: 'object'
+ }
+ subscriptionId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ tags: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ tenantId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ zones: {
+ properties: {}
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ facets: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ resultTruncated: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ totalRecords: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ }
+ Parse_Idle_IP_addresses: {
+ runAfter: {
+ Get_idle_IP_addresses: [
+ 'Succeeded'
+ ]
+ }
+ type: 'ParseJson'
+ inputs: {
+ content: '@body(\'Get_idle_IP_addresses\')'
+ schema: {
+ properties: {
+ properties: {
+ properties: {
+ count: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ data: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ extendedLocation: {
+ properties: {}
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ identity: {
+ properties: {}
+ type: 'object'
+ }
+ kind: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ location: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ managedBy: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ plan: {
+ properties: {}
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ idleTimeoutInMinutes: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ ipTags: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ publicIPAddressVersion: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ publicIPAllocationMethod: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ resourceGuid: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ resourceGroup: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ sku: {
+ properties: {
+ properties: {
+ properties: {
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ tier: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ subscriptionId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ tags: {
+ properties: {}
+ type: 'object'
+ }
+ tenantId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ zones: {
+ properties: {}
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ facets: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ resultTruncated: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ totalRecords: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ }
+ Parse_Idle_Load_Balancers: {
+ runAfter: {
+ Get_idle_Load_Balancers: [
+ 'Succeeded'
+ ]
+ }
+ type: 'ParseJson'
+ inputs: {
+ content: '@body(\'Get_idle_Load_Balancers\')'
+ schema: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ childErrors: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ errorType: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ lineNumber: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ linePosition: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ message: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ path: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ schemaId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ value: {
+ properties: {
+ items: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ }
+ Parse_Idle_disks: {
+ runAfter: {
+ Get_idle_Disks: [
+ 'Succeeded'
+ ]
+ }
+ type: 'ParseJson'
+ inputs: {
+ content: '@body(\'Get_idle_Disks\')'
+ schema: {
+ properties: {
+ properties: {
+ properties: {
+ count: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ data: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ extendedLocation: {
+ properties: {}
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ identity: {
+ properties: {}
+ type: 'object'
+ }
+ kind: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ location: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ managedBy: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ plan: {
+ properties: {}
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ creationData: {
+ properties: {
+ properties: {
+ properties: {
+ createOption: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ imageReference: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ diskIOPSReadWrite: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ diskMBpsReadWrite: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ diskSizeBytes: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ diskSizeGB: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ diskState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ encryption: {
+ properties: {
+ properties: {
+ properties: {
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ hyperVGeneration: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ networkAccessPolicy: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ osType: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ publicNetworkAccess: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ supportedCapabilities: {
+ properties: {
+ properties: {
+ properties: {
+ architecture: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ timeCreated: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ uniqueId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ resourceGroup: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ sku: {
+ properties: {
+ properties: {
+ properties: {
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ tier: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ subscriptionId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ tags: {
+ properties: {}
+ type: 'object'
+ }
+ tenantId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ zones: {
+ properties: {}
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ facets: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ resultTruncated: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ totalRecords: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ }
+ Parse_Snapshots: {
+ runAfter: {
+ Get_Disk_Snapshots_older_than_30_days: [
+ 'Succeeded'
+ ]
+ }
+ type: 'ParseJson'
+ inputs: {
+ content: '@body(\'Get_Disk_Snapshots_older_than_30_days\')'
+ schema: {
+ properties: {
+ query: {
+ type: 'string'
+ }
+ subscriptions: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ }
+ type: 'object'
+ }
+ }
+ }
+ Parse_stopped_VMs: {
+ runAfter: {
+ Get_Stopped_VMs: [
+ 'Succeeded'
+ ]
+ }
+ type: 'ParseJson'
+ inputs: {
+ content: '@body(\'Get_Stopped_VMs\')'
+ schema: {
+ properties: {
+ properties: {
+ properties: {
+ count: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ data: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ extendedLocation: {
+ properties: {}
+ type: 'object'
+ }
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ identity: {
+ properties: {}
+ type: 'object'
+ }
+ kind: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ location: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ managedBy: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ plan: {
+ properties: {}
+ type: 'object'
+ }
+ properties: {
+ properties: {
+ properties: {
+ properties: {
+ diagnosticsProfile: {
+ properties: {
+ properties: {
+ properties: {
+ bootDiagnostics: {
+ properties: {
+ properties: {
+ properties: {
+ enabled: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ extended: {
+ properties: {
+ properties: {
+ properties: {
+ instanceView: {
+ properties: {
+ properties: {
+ properties: {
+ hyperVGeneration: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ powerState: {
+ properties: {
+ properties: {
+ properties: {
+ code: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ displayStatus: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ level: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ hardwareProfile: {
+ properties: {
+ properties: {
+ properties: {
+ vmSize: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ networkProfile: {
+ properties: {
+ properties: {
+ properties: {
+ networkInterfaces: {
+ properties: {
+ items: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ osProfile: {
+ properties: {
+ properties: {
+ properties: {
+ adminUsername: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ allowExtensionOperations: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ computerName: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ requireGuestProvisionSignal: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ secrets: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ windowsConfiguration: {
+ properties: {
+ properties: {
+ properties: {
+ enableAutomaticUpdates: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ patchSettings: {
+ properties: {
+ properties: {
+ properties: {
+ assessmentMode: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ enableHotpatching: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ patchMode: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisionVMAgent: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ provisioningState: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ storageProfile: {
+ properties: {
+ properties: {
+ properties: {
+ dataDisks: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ imageReference: {
+ properties: {
+ properties: {
+ properties: {
+ exactVersion: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ offer: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ publisher: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ sku: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ version: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ osDisk: {
+ properties: {
+ properties: {
+ properties: {
+ caching: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ createOption: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ deleteOption: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ diskSizeGB: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ managedDisk: {
+ properties: {
+ properties: {
+ properties: {
+ id: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ storageAccountType: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ name: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ osType: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ vmId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ resourceGroup: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ sku: {
+ properties: {}
+ type: 'object'
+ }
+ subscriptionId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ tags: {
+ properties: {}
+ type: 'object'
+ }
+ tenantId: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ zones: {
+ properties: {}
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ required: {
+ items: {
+ type: 'string'
+ }
+ type: 'array'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ facets: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ resultTruncated: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ totalRecords: {
+ properties: {
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ type: 'object'
+ }
+ type: {
+ type: 'string'
+ }
+ }
+ type: 'object'
+ }
+ }
+ }
+ Set_alert_recipient: {
+ runAfter: {
+ Set_email_subject: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'SendAlertTo'
+ type: 'string'
+ }
+ ]
+ }
+ }
+ Initialize_App_Gateway_HTML: {
+ runAfter: {
+ Initialize_App_Gateways_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'AppGatewayHTML'
+ type: 'string'
+ value: 'Idle Application Gateway Details
'
+ }
+ ]
+ }
+ }
+ Initialize_Disk_HTML: {
+ runAfter: {
+ Initialize_Disk_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'IdleDiskHTML'
+ type: 'string'
+ value: 'Idle Disk Details
'
+ }
+ ]
+ }
+ }
+ 'Send_an_email_(V2)': {
+ runAfter: {
+ EmailNotice: [
+ 'Succeeded'
+ ]
+ }
+ type: 'ApiConnection'
+ inputs: {
+ host: {
+ connection: {
+ name: '@parameters(\'$connections\')[\'office365\'][\'connectionId\']'
+ }
+ }
+ method: 'post'
+ body: {
+ To: '@variables(\'SendAlertTo\')'
+ Subject: '@variables(\'SetEmailSubject\')'
+ Body: '@{variables(\'EmailNotice\')}
@{variables(\'AppGatewayHTML\')}
@{variables(\'IdleDiskHTML\')}
@{variables(\'IPAddressHTML\')}
@{variables(\'LoadBalancerHTML\')}
@{variables(\'DiskSnapshotHTML\')}
@{variables(\'StoppedVMHTML\')}
📧 About FinOps alerts
FinOps alerts keep you informed about cost optimization opportunities within in your cloud environment. They are fully configurable and can be tailored to run on your desired schedule, ensuring that you receive timely notifications on the scenarios most important to your organization. FinOps alerts are part of the FinOps toolkit, an open-source collection of FinOps solutions that help you manage and optimize your cost, usage, and carbon.
Provide feedback
Give feedback
Vote on or suggest ideas
'
+ }
+ path: '/v2/Mail'
+ }
+ }
+ Initialize_IP_Address_HTML: {
+ runAfter: {
+ Initialize_IP_addresses_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'IPAddressHTML'
+ type: 'string'
+ value: 'Idle IP Address Details
'
+ }
+ ]
+ }
+ }
+ Initialize_Load_Balancer_HTML: {
+ runAfter: {
+ Initialize_Load_Balancer_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'LoadBalancerHTML'
+ type: 'string'
+ value: 'Idle Load Balancer Details
'
+ }
+ ]
+ }
+ }
+ Initialize_Disk_Snapshot_HTML: {
+ runAfter: {
+ Initialize_Snapshot_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'DiskSnapshotHTML'
+ type: 'string'
+ value: 'Old Disk Snapshot Details
'
+ }
+ ]
+ }
+ }
+ Initialize_Stopped_VM_HTML: {
+ runAfter: {
+ Initialize_Stopped_VM_URI: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'StoppedVMHTML'
+ type: 'string'
+ value: 'Stopped VM Details
'
+ }
+ ]
+ }
+ }
+ End_to_App_Gateway_HTML: {
+ runAfter: {
+ For_each_App_GW: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'AppGatewayHTML'
+ value: ''
+ }
+ }
+ End_to_IP_Address_HTML: {
+ runAfter: {
+ For_each_IP_address: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'IPAddressHTML'
+ value: ' '
+ }
+ }
+ End_to_Disk_HTML: {
+ runAfter: {
+ For_each_Disk: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'IdleDiskHTML'
+ value: ''
+ }
+ }
+ End_to_Load_Balancer_HTML: {
+ runAfter: {
+ For_each_Load_Balancer: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'LoadBalancerHTML'
+ value: ''
+ }
+ }
+ End_to_Disk_Snapshot_HTML: {
+ runAfter: {
+ For_each_Snapshot: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'DiskSnapshotHTML'
+ value: ''
+ }
+ }
+ End_to_Stopped_VM_HTML: {
+ runAfter: {
+ For_each_Stopped_VM: [
+ 'Succeeded'
+ ]
+ }
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'StoppedVMHTML'
+ value: ''
+ }
+ }
+ Set_email_subject: {
+ runAfter: {}
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'SetEmailSubject'
+ type: 'string'
+ }
+ ]
+ }
+ }
+ Included_subscriptions: {
+ runAfter: {
+ Set_alert_recipient: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'IncludedSubscriptions'
+ type: 'array'
+ value: []
+ }
+ ]
+ }
+ }
+ Initialize_resources_table: {
+ runAfter: {
+ Excluded_subscriptions: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'resourcesTable'
+ type: 'string'
+ value: 'resources@{if(equals(length(variables(\'IncludedSubscriptions\')), 0), \'\', concat(\'| where subscriptionId in ("\', replace(replace(string(variables(\'IncludedSubscriptions\')), \'[\', \'\'), \']\', \'\'), \'")\'))}@{if(equals(length(variables(\'ExcludedSubscriptions\')), 0), \'\', concat(\'| where subscriptionId !in ("\', replace(replace(string(variables(\'ExcludedSubscriptions\')), \'[\', \'\'), \']\', \'\'), \'")\'))}'
+ }
+ ]
+ }
+ }
+ Condition_App_Gateway: {
+ actions: {
+ Append_no_App_Gateway_results_text: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'AppGatewayHTML'
+ value: 'No resources are idle.'
+ }
+ }
+ }
+ runAfter: {
+ Initialize_App_Gateway_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_App_Gateway_results_in_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'AppGatewayHTML'
+ value: '\n \n | Name | \n Resource Group | \n Subscription | \n
\n'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_idle_App_Gateways\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_Stopped_VMs: {
+ actions: {
+ Append_no_Stopped_VM_results_text_: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'StoppedVMHTML'
+ value: 'No resources are idle.'
+ }
+ }
+ }
+ runAfter: {
+ Initialize_Stopped_VM_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_Stopped_VM_result_in_table_: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'StoppedVMHTML'
+ value: '\n \n | Name | \n Resource Group | \n Subscription | \n
\n'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_Stopped_VMs\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_Disk: {
+ actions: {
+ Append_no_Disk_results_text: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'IdleDiskHTML'
+ value: 'No resources are idle.'
+ }
+ }
+ }
+ runAfter: {
+ Initialize_Disk_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_Disk_results_in_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'IdleDiskHTML'
+ value: '\n \n | Name | \n Resource Group | \n Subscription | \n
\n'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_idle_Disks\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_IP_Address: {
+ actions: {
+ Append_no_IP_Address_results_text: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'IPAddressHTML'
+ value: 'No resources are idle.'
+ }
+ }
+ }
+ runAfter: {
+ Initialize_IP_Address_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_IP_Address_results_in_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'IPAddressHTML'
+ value: '\n \n | Name | \n Resource Group | \n Subscription | \n
\n'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_idle_IP_addresses\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_Load_Balancer: {
+ actions: {
+ Append_no_Load_Balancer_results_text: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'LoadBalancerHTML'
+ value: 'No resources are idle.'
+ }
+ }
+ }
+ runAfter: {
+ Initialize_Load_Balancer_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_Load_Balancer_results_in_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'LoadBalancerHTML'
+ value: '\n \n | Name | \n Resource Group | \n Subscription | \n
\n'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_idle_Load_Balancers\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_Disk_Snapshots: {
+ actions: {
+ Append_no_Disk_Snapshot_results_text: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'DiskSnapshotHTML'
+ value: 'No resources are idle.'
+ }
+ }
+ }
+ runAfter: {
+ Initialize_Disk_Snapshot_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_Disk_Snapshot_results_in_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'DiskSnapshotHTML'
+ value: '\n \n | Name | \n Resource Group | \n Subscription | \n
\n'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_Disk_Snapshots_older_than_30_days\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_App_Gateway_next_steps: {
+ actions: {}
+ runAfter: {
+ End_to_App_Gateway_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_to_App_Gateway_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'AppGatewayHTML'
+ value: '\n 👉 Next steps: Review application gateways which include backend pools with no targets.\n
'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_idle_App_Gateways\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_Disk_next_steps: {
+ actions: {}
+ runAfter: {
+ End_to_Disk_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_to_Disk_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'IdleDiskHTML'
+ value: '\n 👉 Next steps: Review managed disks that are not attached to any virtual machine.\n
'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_idle_Disks\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_IP_Address_next_steps: {
+ actions: {}
+ runAfter: {
+ End_to_IP_Address_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_to_IP_Address_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'IPAddressHTML'
+ value: '\n 👉 Next steps: Review unattached public IP addresses, as they may represent additional cost.\n
'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_idle_IP_addresses\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_Load_Balancer_next_steps: {
+ actions: {}
+ runAfter: {
+ End_to_Load_Balancer_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_to_Load_Balancer_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'LoadBalancerHTML'
+ value: '\n 👉 Next steps: Review load balancers with no backend pools and remove them if not needed.\n
'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_idle_Load_Balancers\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_Disk_Snapshot_next_steps: {
+ actions: {}
+ runAfter: {
+ End_to_Disk_Snapshot_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_to_Disk_Snapshot_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'DiskSnapshotHTML'
+ value: '\n 👉 Next steps: Review managed disk snapshots that are older than 30 days.\n
'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_Disk_Snapshots_older_than_30_days\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ Condition_stopped_VM_next_steps: {
+ actions: {}
+ runAfter: {
+ End_to_Stopped_VM_HTML: [
+ 'Succeeded'
+ ]
+ }
+ else: {
+ actions: {
+ Append_to_Stopped_VM_table: {
+ type: 'AppendToStringVariable'
+ inputs: {
+ name: 'StoppedVMHTML'
+ value: '\n 👉 Next steps: Review stopped VMs, as they are billed for the allocated cost.\n
'
+ }
+ }
+ }
+ }
+ expression: {
+ and: [
+ {
+ equals: [
+ '@length(body(\'Get_Stopped_VMs\')[\'data\'])'
+ 0
+ ]
+ }
+ ]
+ }
+ type: 'If'
+ }
+ EmailNotice: {
+ runAfter: {
+ Condition_App_Gateway_next_steps: [
+ 'Succeeded'
+ ]
+ Condition_Disk_next_steps: [
+ 'Succeeded'
+ ]
+ Condition_IP_Address_next_steps: [
+ 'Succeeded'
+ ]
+ Condition_Load_Balancer_next_steps: [
+ 'Succeeded'
+ ]
+ Condition_Disk_Snapshot_next_steps: [
+ 'Succeeded'
+ ]
+ Condition_stopped_VM_next_steps: [
+ 'Succeeded'
+ ]
+ }
+ type: 'InitializeVariable'
+ inputs: {
+ variables: [
+ {
+ name: 'EmailNotice'
+ type: 'string'
+ value: 'The following resources have been identified through FinOps alerts. Please take a moment to review and proceed with the next steps outlined below:
'
+ }
+ ]
+ }
+ }
+ }
+ outputs: {}
+ }
+ parameters: {
+ '$connections': {
+ value: {
+ office365: {
+ connectionId: apiConnection.id
+ connectionName: connectionName
+ id: '${subscription().id}/providers/Microsoft.Web/locations/${location}/managedApis/office365'
+ }
+ }
+ }
+ }
+ }
+ name: appName
+ location: location
+ tags: {
+ displayName: 'FinOpsalert'
+ }
+}
+
+resource apiConnection 'Microsoft.Web/connections@2016-06-01' = {
+ name: connectionName
+ location: location
+ properties: {
+ api: {
+ id: '${subscription().id}/providers/Microsoft.Web/locations/${location}/managedApis/office365'
+ }
+ displayName:displayName
+ }
+}
+
+//==============================================================================
+// Outputs
+//==============================================================================
+output logicAppName string = finopsAlerts.name
+output connectionName string = connectionName
+output actionsCount int = length(actionKeys)
+output logicAppResourceId string = finopsAlerts.id
+output logicAppPrincipalId string = finopsAlerts.identity.principalId
+output logicAppTriggerUrl string = 'https://${finopsAlerts.name}.logic.azure.com:443/workflows/${finopsAlerts.name}/triggers/Recurrence/run?api-version=2016-10-01'