Update budget amount on existing budget by bicep file - azure

I have already budgets created by the bicep and work perfectly. But I want to change the amount on an existing budget by bicep file. Is it possible to do that? I'm receiving errors with the date, or is the budget already exist, .... finally I cannot change it. Below my bicep files:
targetScope = 'subscription'
#description('Name of the Budget. It should be unique within a resource group.')
param budgetName string = 'ConsumptionBudget'
#description('The total amount of cost or usage to track with the budget')
param budgetAmount int
#description('Threshold value associated with a notification. Notification is sent when the cost exceeded the threshold. It is always percent and has to be between 0.01 and 1000.')
param firstPercentageThreshold int = 75
#description('First action group name')
param firstActionGroup string
#description('Threshold value associated with a notification. Notification is sent when the cost exceeded the threshold. It is always percent and has to be between 0.01 and 1000.')
param secondPercentageThreshold int = 100
#description('Second action group name')
param secondActionGroup string
#description('Threshold value associated with a notification. Notification is sent when the cost exceeded the threshold. It is always percent and has to be between 0.01 and 1000.')
param thirdPercentageThreshold int = 110
#description('First action group name')
param thirdActionGroup string
#description('The list of email addresses to send the budget notification to when the threshold is exceeded.')
param contactEmails array = []
param contactRoles array = [
'Owner'
]
#description('The start date must be first of the month in YYYY-MM-DD format. Future start date should not be more than three months. Past start date should be selected within the timegrain preiod.')
param startDate string = '${utcNow('yyyy-MM')}-01'
#description('The time covered by a budget. Tracking of the amount will be reset based on the time grain.')
#allowed([
'Monthly'
'Quarterly'
'Annually'
])
param timeGrain string = 'Monthly'
#description('Subscription id on which budget will be created')
param subscriptionId string
#description('Resource group name in which action groups are located - Action groups passed in firstActionGroup, secondActionGroup, thirdActionGroup')
param resourceGroupName string
var category = 'Cost'
var operator = 'GreaterThan'
resource budgetExists 'Microsoft.Consumption/budgets#2021-10-01' existing = {
name: budgetName
}
resource budget 'Microsoft.Consumption/budgets#2021-10-01' = if(budgetExists.id==null){
name: budgetName
properties: {
timePeriod: {
startDate: startDate
}
timeGrain: timeGrain
category: category
amount: budgetAmount
notifications: {
NotificationForExceededBudget1: {
enabled: true
contactEmails: contactEmails
threshold: firstPercentageThreshold
operator: operator
contactRoles: contactRoles
contactGroups: [
'/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/actionGroups/${firstActionGroup}'
]
}
NotificationForExceededBudget2: {
enabled: true
contactEmails: contactEmails
threshold: secondPercentageThreshold
operator: operator
contactRoles: contactRoles
contactGroups: [
'/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/actionGroups/${secondActionGroup}'
]
}
NotificationForExceededBudget3: {
enabled: true
contactEmails: contactEmails
threshold: thirdPercentageThreshold
operator: operator
contactRoles: contactRoles
contactGroups: [
'/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/actionGroups/${thirdActionGroup}'
]
}
}
}
}
resource budgetUpdate 'Microsoft.Consumption/budgets#2021-10-01' = if(budgetExists.id != null) {
name: budgetName
eTag: budgetExists.eTag
properties: {
timePeriod: {
startDate: budgetExists.properties.timePeriod.startDate
endDate: budgetExists.properties.timePeriod.endDate
}
timeGrain: budgetExists.properties.timeGrain
category: budgetExists.properties.category
amount: budgetAmount
filter: {}
notifications: {
NotificationForExceededBudget1: {
enabled: true
contactEmails: contactEmails
threshold: firstPercentageThreshold
operator: operator
contactRoles: contactRoles
contactGroups: [
'/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/actionGroups/${firstActionGroup}'
]
}
NotificationForExceededBudget2: {
enabled: true
contactEmails: contactEmails
threshold: secondPercentageThreshold
operator: operator
contactRoles: contactRoles
contactGroups: [
'/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/actionGroups/${secondActionGroup}'
]
}
NotificationForExceededBudget3: {
enabled: true
contactEmails: contactEmails
threshold: thirdPercentageThreshold
operator: operator
contactRoles: contactRoles
contactGroups: [
'/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/actionGroups/${thirdActionGroup}'
]
}
}
}
}
Below exceptions from pipelines:
nner Errors:
2023-01-31T08:08:56.4475545Z {"code": "InvalidTemplate", "target": "/subscriptions/d955395e-9c5f-4fa0-ad91-c46ea57d4646/providers/Microsoft.Resources/deployments/budget_20230131080855", "message": "Deployment template validation failed: 'The resource 'Microsoft.Consumption/budgets/ConsumptionBudget' at line '1' and column '3675' is defined multiple times in a template. Please see https://aka.ms/arm-syntax-resources for usage details.'.", "additionalInfo": [{"type": "TemplateViolation", "info": {"lineNumber": 1, "linePosition": 3675, "path": "properties.template.resources[0]"}}]}

This is because you cannot define same resource in a deployment twice :) Looking at your bicep Microsoft.Consumption/budgets/ is indeed defined twice, just with a different condition. Having condition doesn't count, it is still a duplicate :|
What is more, the way you check for existence won't work, as 'existing' keyword in bicep is simply translated to subscriptionResourceId('Microsoft.Consumption/budgets', parameters('budgetName') while compiling to ARM, and this will never be NULL as it is just a helper method to calculate full resource ID, not checking its existence.

Related

Log Query Alert Example

I am designing a monitoring solution for a project and would like to create some alert rules for certain resources (for example application insights).
If I'd like to set up a log search alert, I need to define a specific query and tell the alert what to do.
However, I have not written a log query alert before and do not know how I could set that up.
Currently, I have written an example for a log search in Bicep:
#description('Location of the resource.')
param location string
#description('Log Analytics workspace ID to associate with your Application Insights resource.')
param workspaceId string
#allowed([
0
1
2
3
4])
#description('Severity of the alert.')
param severity int = 2
resource appInsightsLogRule 'Microsoft.Insights/scheduledQueryRules#2022-06-15' = {
name: appInsightsLogRuleName
location: location
properties: {
displayName: appInsightsLogRuleName
severity: severity
enabled: true
evaluationFrequency: 'PT5M'
scopes: [
workspaceId
]
targetResourceTypes: [
'Microsoft.Insights/components'
]
windowSize: 'PT5M'
criteria: {
allOf: [
{
query: 'tbd.'
timeAggregation: 'Count'
dimensions: []
operator: 'GreaterThan'
threshold: 0
failingPeriods: {
numberOfEvaluationPeriods: 1
minFailingPeriodsToAlert: 3
}
}
]
}
autoMitigate: true
actions: {
actionGroups: [
actiongroups_team_blue
]
}
}
}
The query is currently still empty, as I don't know how I could fill this one.
Could someone maybe please share samples or queries for a useful scenario (for example Application Insights, Network Watcher, Sentinel, etc.) for a scheduledQueryAlert or general alert rule? Thank you very much!
First of all, Check the parameter.json file to avoid these kind of empty output issues and check whether the given query is valid.
Referring to MSDoc, I tried to create a sample scheduled log alert for log analytics workspace resource and verify that it was sent to the given email address. It worked and was successfully deployed as follows.
#description('Log Analytics workspace Resource ID.')
param sourceId string = ''
param location string = ''
param actionGroupId string = ''
resource logQueryAlert 'Microsoft.Insights/scheduledQueryRules#2018-04-16' = {
name: 'xxxxx log query alert'
location: location
properties: {
description: 'This is a sample alert'
enabled: 'true'
source: {
query: 'Event | where EventLevelName == "warning" | summarize count() by Computer' #query as per the requirement
dataSourceId: sourceId
queryType: 'ResultCount'
}
schedule: {
frequencyInMinutes: 15
timeWindowInMinutes: 60
}
action: {
'odata.type': 'Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction'
severity: '4'
aznsAction: {
actionGroup: array(actionGroupId)
emailSubject: 'xxxx Log Alert mail subject'
customWebhookPayload: '{ "alertname":"#samplealertrulename", "IncludeSearchResults":true }'
}
trigger: {
thresholdOperator: 'GreaterThan'
threshold: 1
}
}
}
}
Deployment succeeded:
Azure Portal:
Log query alert:
Mail triggered successfully:

Azure Service Bus autoscaling in bicep template

On our project we are using Service Bus, for the Infrastructure as Code we are using bicep template in order to deploy this Service Bus. Lately we came to the conclusion that we should enable scaling for our service bus as we were having some serious issues with the capacity of just 1 single Message Unit.
Does anyone know if there is anyway to add the autoscaling in the bicep template for a service bus Azure Resource ? I cannot really find something in the MS official documentation.
Google didn't helped ...
If you have a look at the documentation, there is an ARM template that show how to configure autoscaling:
Automatically update messaging units of an Azure Service Bus namespace.
The equivalent bicep (using az bicep decompile) looks like this:
#description('Name of the Service Bus namespace')
param serviceBusNamespaceName string
#description('Name of the Queue')
param serviceBusQueueName string
#description('Name of the auto scale setting.')
param autoScaleSettingName string
#description('Location for all resources.')
param location string = resourceGroup().location
resource serviceBusNamespaceName_resource 'Microsoft.ServiceBus/namespaces#2021-11-01' = {
name: serviceBusNamespaceName
location: location
sku: {
name: 'Premium'
}
properties: {
}
}
resource serviceBusNamespaceName_serviceBusQueueName 'Microsoft.ServiceBus/namespaces/queues#2021-11-01' = {
parent: serviceBusNamespaceName_resource
name: '${serviceBusQueueName}'
properties: {
lockDuration: 'PT5M'
maxSizeInMegabytes: 1024
requiresDuplicateDetection: false
requiresSession: false
defaultMessageTimeToLive: 'P10675199DT2H48M5.4775807S'
deadLetteringOnMessageExpiration: false
duplicateDetectionHistoryTimeWindow: 'PT10M'
maxDeliveryCount: 10
autoDeleteOnIdle: 'P10675199DT2H48M5.4775807S'
enablePartitioning: false
enableExpress: false
}
}
resource autoScaleSettingName_resource 'Microsoft.Insights/autoscaleSettings#2021-05-01-preview' = {
name: autoScaleSettingName
location: 'East US'
tags: {
}
properties: {
name: autoScaleSettingName
enabled: true
predictiveAutoscalePolicy: {
scaleMode: 'Disabled'
scaleLookAheadTime: null
}
targetResourceUri: serviceBusNamespaceName_resource.id
profiles: [
{
name: 'Increase messaging units to 2 on weekends'
capacity: {
minimum: '2'
maximum: '2'
default: '2'
}
rules: []
recurrence: {
frequency: 'Week'
schedule: {
timeZone: 'Eastern Standard Time'
days: [
'Saturday'
'Sunday'
]
hours: [
6
]
minutes: [
0
]
}
}
}
{
name: '{"name":"Scale Out at 75% CPU and Scale In at 25% CPU","for":"Increase messaging units to 4 on weekends"}'
capacity: {
minimum: '1'
maximum: '8'
default: '2'
}
rules: [
{
scaleAction: {
direction: 'Increase'
type: 'ServiceAllowedNextValue'
value: '1'
cooldown: 'PT5M'
}
metricTrigger: {
metricName: 'NamespaceCpuUsage'
metricNamespace: 'microsoft.servicebus/namespaces'
metricResourceUri: serviceBusNamespaceName_resource.id
operator: 'GreaterThan'
statistic: 'Average'
threshold: 75
timeAggregation: 'Average'
timeGrain: 'PT1M'
timeWindow: 'PT10M'
dimensions: []
dividePerInstance: false
}
}
{
scaleAction: {
direction: 'Decrease'
type: 'ServiceAllowedNextValue'
value: '1'
cooldown: 'PT5M'
}
metricTrigger: {
metricName: 'NamespaceCpuUsage'
metricNamespace: 'microsoft.servicebus/namespaces'
metricResourceUri: serviceBusNamespaceName_resource.id
operator: 'LessThan'
statistic: 'Average'
threshold: 25
timeAggregation: 'Average'
timeGrain: 'PT1M'
timeWindow: 'PT10M'
dimensions: []
dividePerInstance: false
}
}
]
recurrence: {
frequency: 'Week'
schedule: {
timeZone: 'Eastern Standard Time'
days: [
'Saturday'
'Sunday'
]
hours: [
18
]
minutes: [
0
]
}
}
}
]
notifications: []
targetResourceLocation: 'East US'
}
}

Use of TypeSet vs TypeList in Terraform when building a custom provider

I'm developing a terraform provider by following this guide.
However I stumbled upon using TypeList vs TypeSet:
TypeSet implements set behavior and is used to represent an unordered collection of items, meaning that their ordering specified does not need to be consistent, and the ordering itself has no impact on the behavior of the resource.
TypeList is used to represent an ordered collection of items, where the order the items are presented can impact the behavior of the resource being modeled. An example of ordered items would be network routing rules, where rules are examined in the order they are given until a match is found. The items are all of the same type defined by the Elem property.
My resource require one of 2 blocks to be present, i.e.:
resource "foo" "example" {
name = "123"
# Only one of basketball / football are expected to be present
basketball {
nba_id = "23"
}
football {
nfl_id = "1"
}
}
and my schema looks the following:
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
},
"basketball": basketballSchema(),
"football": footballSchema(),
},
func basketballSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"nba_id": {
Type: schema.TypeString,
Required: true,
},
},
},
ExactlyOneOf: ["basketball", "football"],
}
}
func footballSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"nfl_id": {
Type: schema.TypeString,
Required: true,
},
},
},
ExactlyOneOf: ["basketball", "football"],
}
}
Is that accurate that both TypeSet and TypeList will work in this scenario where we restrict the number of elements to either 0 or just 1?

Why can't I loop twice in an Azure resource module?

I am trying to set up alerts in Azure bicep resources/modules. The goal is to have 3 files, one where the initial resource structure is created and parameterized:
param metricAlertsName string
param description string
param severity int
param enabled bool = true
param scopes array = []
param evaluationFrequency string
param windowSize string
param targetResourceRegion string = resourceGroup().location
param allOf array = []
param actionGroupName string
var actionGroupId = resourceId(resourceGroup().name, 'microsoft.insights/actionGroups', actionGroupName)
resource dealsMetricAlerts 'Microsoft.Insights/metricAlerts#2018-03-01' = [for appService in appServices : {
name: metricAlertsName
location: 'global'
tags: {}
properties: {
description: description
severity: severity
enabled: enabled
scopes: scopes
evaluationFrequency: evaluationFrequency
windowSize: windowSize
targetResourceRegion: targetResourceRegion
criteria: {
'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
allOf: allOf
}
actions: [
{
actionGroupId: actionGroupId
}
]
}
}]
A second file, main.bicep where all of my resources are being consumed as modules:
module dealsMetricAlerts '../modules/management/metric-alert.bicep' = [for (alert, index) in alertConfig.metricAlerts: {
name: alert.name
params: {
metricAlertsName: alert.params.metricAlertsName
actionGroupName: actionGroupName
description: alert.params.description
evaluationFrequency: alert.params.evaluationFrequency
severity: alert.params.severity
windowSize: alert.params.windowSize
allOf: alert.params.allOf
scopes: [
resourceId(resourceGroup().name,'Microsoft.Web/sites', 'gbt-${env1}-${env}-${location}-webapp')
]
}
}]
And the third file which is a parameter file in which I can loop over, enter its values into the module in main.bicep to create multiple resources without having to write more than one module:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"alertConfig": {
"value": {
"metricAlerts": [
{
"name": "403ErrorTest",
"params": {
"metricAlertsName": "AppService403ErrorTest",
"actionGroupName": "actionGroupName",
"description": "Alert fires whenever the App Service throws a 403 error",
"evaluationFrequency": "PT5M",
"severity": 2,
"windowSize": "PT15M",
"allOf": [
{
"name": "403errorTest",
"metricName": "Http403",
"dimensions": [],
"operator": "GreaterThan",
"threshold": 0,
"timeAggregation": "Count"
}
]
}
},
{
"name": "5XXErrorTest",
"params": {
// Values for 500 error
}
}
]
}
}
}
}
I have a total of 5 app services, 1 web app and 4 function apps. If I was to hard code the name of the app service like above then that one web app is deployed with the 2 errors exactly as it should.
The issue that I'm having is to try and use another loop on the scopes param to have the 2 errors attached to all 5 app services.
I tried something like:
// Created an array with the names of the app services
var appServices = [
'dda-webApp'
'dealservice-fnapp'
'itemdata-fnapp'
'refdata-fnapp'
'userprofile-fnapp'
]
// Update the scopes param to loop through the names\
scopes: [for appService in appServices: [
resourceId(resourceGroup().name,'Microsoft.Web/sites', 'gbt-${env1}-${env}-${location}-${appService}')
]]
This most often gives me an error saying: Unexpected token array in scopes[0].
I believe what's happening is when it's trying to look up the resource by name in resourceId it's looping through all of the appServices names and attaching the entire array onto the resource name making it invalid.
Is there another way to loop through these scopes in order to attach the 403 and 500 alerts to each of my 5 app services?
Looking at the documentation, you can't do inner loops:
Using loops in Bicep has these limitations:
Loop iterations can't be a negative number or exceed 800 iterations.
Can't loop a resource with nested child resources. Change the child resources to top-level resources. See Iteration for a child resource.
Can't loop on multiple levels of properties.
In you case, you can define a variable and create the array outside of the module loop:
// Created an array with the names of the app services
var appServices = [
'dda-webApp'
'dealservice-fnapp'
'itemdata-fnapp'
'refdata-fnapp'
'userprofile-fnapp'
]
// Create an array of app service resource ids
var appServiceIds = [for appService in appServices: resourceId(resourceGroup().name,'Microsoft.Web/sites', 'gbt-${env1}-${env}-${location}-${appService}')]
// Set the scope param with this variable
module dealsMetricAlerts '../modules/management/metric-alert.bicep' = [for (alert, index) in alertConfig.metricAlerts: {
name: alert.name
params: {
...
scopes: appServiceIds
}
}]

Retrieve related invoice of an applied payment in NetSuite SuiteScript 2.0

I have a requirement; where I need to load a list of payments that were updated between a specified date/time range. And for each invoice; I need to get a list of internal id (aka invoice id) that payment has been applied to (since a payment can be applied to one or more invoices).
I tried to achieve this using a search query like this:
var paymentSearch = search.create({
type: search.Type.CUSTOMER_PAYMENT,
filters: [
['lastmodifieddate', 'within', from_datetime, to_datetime],
'and',
['appliedToTransaction.tranid', search.Operator.ISNOTEMPTY, '#NONE']
],
columns: [
'entity',
'tranid',
search.createColumn({
name: 'internalid',
join: 'appliedToTransaction'
})
]
});
for (var i = 0; i < paymentsPagedData.pageRanges.length; i++) {
var currentPage = paymentsPagedData.fetch(i);
currentPage.data.forEach(function(result) {
// TEST
var appliedToInvoiceIds = result.getValue({ name: 'internalid', join: 'appliedToTransaction' });
}
}
When I inspected the appliedToInvoiceIds, it only appears to be returning a single value, the first invoice this payment has been applied to. How can I get all of the invoice ids that the payment has been applied to?
I tried inspecting the result object (inside the forEach loop) to see what was inside and this is what I saw:
{
"recordType": "customerpayment",
"id": "25911",
"values": {
"entity": [
{
"value": "761",
"text": "COMPANY NAME INC"
}
],
"tranid": "722",
"appliedToTransaction.internalid": [
{
"value": "2676",
"text": "2676"
},
{
"value": "2658",
"text": "2658"
}
]
}
}
As you can see on the result object of the payment; it has appliedToTransaction.internalid which is an array and has two items in it. How can I retrieve these?
Is there an alternate version of result.getValue(...) available for retrieving array of items/field values via a join?
You need to return true at the end of your forEach. Otherwise it will only return one result.

Resources