I have a bicep file which is supposed to create a virtual network gateway. I have broken it down by having a seperate bicep file that creates the virtual network. What I really ought to do is have a module for the vnet creation and another for the virtual network gateway, as bicep is new to me, I prefer to do things with little steps and then improve.
What am I trying to achieve ?
Create a virtual network gateway using bicep. I have it as an ARM template which currently works. I converted the ARM template to a bicep file, and this has failed to successfully deploy.
param location string = resourceGroup().location
param rg string = resourceGroup().name
param virtual_network_name string = 'my_virtual_network'
param gwSubnetName string = 'myGatewaySubnet'
param public_ip_gateway string = 'my_public_ip'
param p2s_vpn_name string = 'myPoint_toSite'
param p2s_subnet_name string = 'p2s_subnet'
resource public_ip_gateway_resource 'Microsoft.Network/publicIPAddresses#2022-01-01' = {
name: public_ip_gateway
location: location
sku: {
name: 'Basic'
tier: 'Regional'
}
properties: {
ipAddress: '40.161.130.50'
publicIPAddressVersion: 'IPv4'
publicIPAllocationMethod: 'Dynamic'
idleTimeoutInMinutes: 4
ipTags: []
}
}
resource virtual_network_name_GatewaySubnet 'Microsoft.Network/virtualNetworks/subnets#2022-01-01' = {
name: '${virtual_network_name}/GatewaySubnet'
properties: {
addressPrefix: '10.2.255.0/25'
delegations: []
privateEndpointNetworkPolicies: 'Disabled'
privateLinkServiceNetworkPolicies: 'Enabled'
}
dependsOn: [
virtual_network_name_resource
]
}
resource virtual_network_name_p2s_subnet 'Microsoft.Network/virtualNetworks/subnets#2022-01-01' = {
name: '${virtual_network_name}/p2s_subnet'
properties: {
addressPrefix: '10.2.1.0/24'
serviceEndpoints: []
delegations: []
privateEndpointNetworkPolicies: 'Disabled'
privateLinkServiceNetworkPolicies: 'Enabled'
}
dependsOn: [
virtual_network_name_resource
]
}
resource p2s_vpn_name_resource 'Microsoft.Network/virtualNetworkGateways#2022-01-01' = {
name: p2s_vpn_name
location: location
properties: {
enablePrivateIpAddress: false
ipConfigurations: [
{
name: 'default'
id: '${p2s_vpn_name_resource.id}/ipConfigurations/default'
properties: {
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: public_ip_gateway_resource.id
}
subnet: {
id: virtual_network_name_GatewaySubnet.id
}
}
}
]
natRules: []
enableBgpRouteTranslationForNat: false
disableIPSecReplayProtection: false
sku: {
name: 'VpnGw1'
tier: 'VpnGw1'
}
gatewayType: 'Vpn'
vpnType: 'RouteBased'
enableBgp: false
activeActive: false
vpnClientConfiguration: {
vpnClientAddressPool: {
addressPrefixes: [
'119.x.x.0/24'
]
}
vpnClientProtocols: [
'OpenVPN'
]
vpnAuthenticationTypes: [
'Certificate'
]
vpnClientRootCertificates: [
{
name: 'Rahman'
id: '${p2s_vpn_name_resource.id}/vpnClientRootCertificates/MyCert'
properties: {
publicCertData: 'xxxxxxxxxxx=='
}
}
]
vpnClientRevokedCertificates: []
radiusServers: []
vpnClientIpsecPolicies: []
}
bgpSettings: {
asn: 65515
bgpPeeringAddress: '10.2.255.126'
peerWeight: 0
bgpPeeringAddresses: [
{
ipconfigurationId: '${p2s_vpn_name_resource.id}/ipConfigurations/default'
customBgpIpAddresses: []
}
]
}
customRoutes: {
addressPrefixes: []
}
vpnGatewayGeneration: 'Generation1'
}
}
resource virtual_network_name_resource 'Microsoft.Network/virtualNetworks#2022-01-01' = {
name: virtual_network_name
location: location
properties: {
addressSpace: {
addressPrefixes: [
'10.2.0.0/16'
]
}
subnets: [
{
name: 'GatewaySubnet'
id: resourceId(rg, 'Microsoft.Network/virtualNetworks/subnets', virtual_network_name ,gwSubnetName)
properties: {
addressPrefix: '10.2.255.0/25'
delegations: []
privateEndpointNetworkPolicies: 'Disabled'
privateLinkServiceNetworkPolicies: 'Enabled'
}
type: 'Microsoft.Network/virtualNetworks/subnets'
}
{
name: 'p2s_subnet'
// id: virtual_network_name_p2s_subnet.id
id: resourceId(rg, 'Microsoft.Network/virtualNetworks/subnets', virtual_network_name ,p2s_subnet_name)
properties: {
addressPrefix: '10.1.1.0/24'
serviceEndpoints: []
delegations: []
privateEndpointNetworkPolicies: 'Disabled'
privateLinkServiceNetworkPolicies: 'Enabled'
}
type: 'Microsoft.Network/virtualNetworks/subnets'
}
]
virtualNetworkPeerings: []
enableDdosProtection: false
}
}
what have I done ?
I have tried to get around circular reference issues by creating the virtual network first, and referencing it using the example below.
instead of id: virtual_network_name_p2s_subnet.id I have changed it to
id: resourceId(rg, 'Microsoft.Network/virtualNetworks/subnets', virtual_network_name ,p2s_subnet_name)
However I'm unsure as to how to deal with cases like
ipconfigurationId: '${p2s_vpn_name_resource.id}/ipConfigurations/default' when the virtual networ gateway has not been created, I have no way of knowing what the ipconfigurationID would be.
This leads to the error
This expression is referencing its own declaration, which is not allowed.bicep(BCP079)
Few things here,
You're defining the subnets multiple times: in the vnet resource and separately. You only need to define them once inside the vnet resource. As you suggested you could then reference them like that:
resourceId(rg, 'Microsoft.Network/virtualNetworks/subnets', virtual_network_name_resource.name ,p2s_subnet_name)
If you need to reference the same resource, you could always do it like that as well:
resourceId('Microsoft.Network/virtualNetworkGateways/ipConfigurations', p2s_vpn_name, 'default')
Here is is simplified version of your template:
param location string = resourceGroup().location
param virtual_network_name string = 'my_virtual_network'
param gwSubnetName string = 'myGatewaySubnet'
param public_ip_gateway string = 'my_public_ip'
param p2s_vpn_name string = 'myPoint_toSite'
param p2s_subnet_name string = 'p2s_subnet'
resource public_ip_gateway_resource 'Microsoft.Network/publicIPAddresses#2022-01-01' = {
name: public_ip_gateway
location: location
sku: {
name: 'Basic'
tier: 'Regional'
}
properties: {
ipAddress: '40.161.130.50'
publicIPAddressVersion: 'IPv4'
publicIPAllocationMethod: 'Dynamic'
idleTimeoutInMinutes: 4
ipTags: []
}
}
resource virtual_network_resource 'Microsoft.Network/virtualNetworks#2022-01-01' = {
name: virtual_network_name
location: location
properties: {
addressSpace: {
addressPrefixes: [
'10.2.0.0/16'
]
}
subnets: [
{
name: gwSubnetName
properties: {
addressPrefix: '10.2.255.0/25'
delegations: []
privateEndpointNetworkPolicies: 'Disabled'
privateLinkServiceNetworkPolicies: 'Enabled'
}
type: 'Microsoft.Network/virtualNetworks/subnets'
}
{
name: p2s_subnet_name
properties: {
addressPrefix: '10.1.1.0/24'
serviceEndpoints: []
delegations: []
privateEndpointNetworkPolicies: 'Disabled'
privateLinkServiceNetworkPolicies: 'Enabled'
}
type: 'Microsoft.Network/virtualNetworks/subnets'
}
]
virtualNetworkPeerings: []
enableDdosProtection: false
}
}
resource p2s_vpn_name_resource 'Microsoft.Network/virtualNetworkGateways#2022-01-01' = {
name: p2s_vpn_name
location: location
properties: {
enablePrivateIpAddress: false
ipConfigurations: [
{
name: 'default'
properties: {
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: public_ip_gateway_resource.id
}
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', virtual_network_resource.name, gwSubnetName)
}
}
}
]
natRules: []
enableBgpRouteTranslationForNat: false
disableIPSecReplayProtection: false
sku: {
name: 'VpnGw1'
tier: 'VpnGw1'
}
gatewayType: 'Vpn'
vpnType: 'RouteBased'
enableBgp: false
activeActive: false
vpnClientConfiguration: {
vpnClientAddressPool: {
addressPrefixes: [
'119.x.x.0/24'
]
}
vpnClientProtocols: [
'OpenVPN'
]
vpnAuthenticationTypes: [
'Certificate'
]
vpnClientRootCertificates: [
{
name: 'Rahman'
properties: {
publicCertData: 'xxxxxxxxxxx=='
}
}
]
vpnClientRevokedCertificates: []
radiusServers: []
vpnClientIpsecPolicies: []
}
bgpSettings: {
asn: 65515
bgpPeeringAddress: '10.2.255.126'
peerWeight: 0
bgpPeeringAddresses: [
{
ipconfigurationId: resourceId('Microsoft.Network/virtualNetworkGateways/ipConfigurations', p2s_vpn_name, 'default')
customBgpIpAddresses: []
}
]
}
customRoutes: {
addressPrefixes: []
}
vpnGatewayGeneration: 'Generation1'
}
}
I woudl like to create a Azure Dashboard template to monitor CPU percentage for my App Service Plan. Currently It look like this:
resource dashboardName_resource 'Microsoft.Portal/dashboards#2020-09-01-preview' = {
name: dashboardName
location: location
properties: {
lenses: [
{
order: 0
parts: [
{
position: {
x: 0
y: 4
rowSpan: 3
colSpan: 11
}
metadata: {
inputs: [
{
name: 'queryInputs'
value: {
timespan: {
duration: 'PT1H'
}
id: resourceId(appServicePlanResourceGroup, 'Microsoft.Web/serverfarms', appServicePlanName)
chartType: 0
metrics: [
{
name: 'CPU Percentage'
resourceId: resourceId(appServicePlanResourceGroup, 'Microsoft.Web/serverfarms', appServicePlanName)
}
]
}
}
]
type: 'Extension/Microsoft_Azure_Monitoring/PartType/MetricsChartPart'
}
}
]
}
]
}
}
The validation & deployment was succesfull, but when I get to this dashboard i got this:
Does anyon knows why?
By referring a document from MS sample replica of your bicep code is created and was able to deploy it successfully.
While I am doing a repro, I have changed type extension in .bicep file fromtype:'Extension/Microsoft_Azure_Monitoring/PartType/MetricsChartPart to type:'Extension/HubsExtension/PartType/MonitorChartPart' which got succeeded and able to view the CPU metrics.
Attaching a sample code for your reference:
param location string = resourceGroup().location
param appServicePlanResourceGroup string = 'xxxxx'
param appServicePlanName string = 'xxxxxxx'
resource symbolicname 'Microsoft.Portal/dashboards#2020-09-01-preview' = {
name: 'xxxxmydemo'
location: location
tags: {
demo: 'repro01'
}
properties: {
lenses: [
{
metadata: {}
order: 2
parts: [
{
metadata: {
type: 'Extension/HubsExtension/PartType/MonitorChartPart'
inputs: [
{
name: 'queryInputs'
value: {
timespan: {
duration: 'PT1H'
}
id: resourceId(appServicePlanResourceGroup, 'Microsoft.Web/serverfarms', appServicePlanName)
chartType: 0
metrics: [
{
name: 'CPU Percentage'
resourceId: resourceId(appServicePlanResourceGroup, 'Microsoft.Web/serverfarms', appServicePlanName)
}
]
}
}
]
}
position: {
colSpan: 11
metadata: {}
rowSpan: 5
x: 3
y: 4
}
}
]
}
]
metadata: {}
}
}
Output for your reference:
Note: Validate access control(IAM) permissions for the resource group ideally and it should be part of it.
I have a Bicep template to create an Azure SignalR Service per the following script. How can I obtain the upstream's code value within the bicep template and populate the urlTemplate's code value based on it? (the keyword TBD exhibits the exact spot in the following code.)
targetScope = 'resourceGroup'
param location string = resourceGroup().location
param signalRName string
resource signalR 'Microsoft.SignalRService/signalR#2022-02-01' = {
name: signalRName
location: location
kind: 'SignalR'
sku: {
name: 'Free_F1'
tier: 'Free'
capacity: 1
}
properties: {
features: [
{
flag: 'ServiceMode'
value: 'Serverless'
}
{
flag: 'EnableConnectivityLogs'
value: 'true'
}
{
flag: 'EnableMessagingLogs'
value: 'true'
}
{
flag: 'EnableLiveTrace'
value: 'true'
}
]
liveTraceConfiguration: {
enabled: 'true'
categories: [
{
name: 'ConnectivityLogs'
enabled: 'true'
}
{
name: 'MessagingLogs'
enabled: 'true'
}
{
name: 'HttpRequestLogs'
enabled: 'true'
}
]
}
cors: {
allowedOrigins: [
'*'
]
}
upstream: {
templates: [
{
hubPattern: '*'
eventPattern: '*'
categoryPattern: '*'
auth: {
type: 'None'
}
urlTemplate: 'https://${signalRName}.azurewebsites.net/runtime/webhooks/signalr?code=TBD'
}
]
}
}
}
I'm assuming that you are using a function app that has the same name as your signalR service.
Looking at the documentation:
The url always looks like that <Function_App_URL>/runtime/webhooks/signalr?code=<API_KEY>
the API_KEY is a function app systemkey called signalr_extension.
So you should be able to retrieve the key and use it like that:
var signalRKey = listKeys(resourceId('Microsoft.Web/sites/host', signalRName, 'default'), '2022-03-01').systemkeys.signalr_extension
resource signalR 'Microsoft.SignalRService/signalR#2022-02-01' = {
name: signalRName
...
properties: {
...
upstream: {
templates: [
{
...
urlTemplate: 'https://${signalRName}.azurewebsites.net/runtime/webhooks/signalr?code=${signalRKey}'
}
]
}
}
}
I tried callingsetFilter function on my Tabulator tree structure, in order to filter out items. It seems to only filter out top parents. Any idea how to make this work for any level (any children or parents)? http://tabulator.info/docs/4.1/tree doesn't say much about how filtering works.
Function
table.setFilter('id', '=', 214659) is not returning anything...
Tree structure
[
{
"level":0,
"name":"word1",
"id":125582,
"_children":[
{
"level":1,
"name":"word6",
"id":214659
},
{
"level":1,
"name":"word7",
"id":214633
},
{
"level":1,
"name":"word2",
"id":214263,
"_children":[
{
"level":2,
"name":"word8",
"id":131673
},
{
"level":2,
"name":"word9",
"id":125579
},
{
"level":2,
"name":"word10",
"id":125578
},
{
"level":2,
"name":"word4",
"id":172670,
"_children":[
{
"level":3,
"name":"word13",
"id":172669
},
{
"level":3,
"name":"word14",
"id":174777
},
{
"level":3,
"name":"word5",
"id":207661,
"_children":[
{
"level":4,
"name":"word15",
"id":216529
},
{
"level":4,
"name":"word16",
"id":223884,
"_children":[
{
"level":5,
"name":"word17",
"id":223885,
"_children":[
{
"level":6,
"name":"word18",
"id":229186,
"_children":[
{
"level":7,
"name":"word19",
"id":219062
},
{
"level":7,
"name":"word20",
"id":222243
}
]
}
]
}
]
}
]
}
]
},
{
"level":2,
"name":"word3",
"id":214266,
"_children":[
{
"level":3,
"name":"word11",
"id":216675
},
{
"level":3,
"name":"word12",
"id":216671
}
]
}
]
}
]
}
]
After a little searching found out an extension for lodash library called deepdash which has deep level filtering and it works quite well.
You will have 2 new dependencies but I think it will serve your purpose.
Check the documentation on how to install them here
In the snippet here you can see in the log the results. I made a sandbox also here
This is for a list of ids, one or more.
If you need only for one value change the conditional. return _.indexOf(idList, value.id) !== -1; to return id===value.id; where id is your id variable
Also after looking at the documentation from Tabulator, the have only one level filtering, even if you write your own custom filter it wouldn't help, because it expects a bool value to render the row or not. But only for the first level, so if the parent is not what you look for the child will be ignored. The only option for you is to filter the data outside the Tabulator.
const data = [
{
level: 0,
name: "word1",
id: 125582,
_children: [
{
level: 1,
name: "word6",
id: 214659
},
{
level: 1,
name: "word7",
id: 214633
},
{
level: 1,
name: "word2",
id: 214263,
_children: [
{
level: 2,
name: "word8",
id: 131673
},
{
level: 2,
name: "word9",
id: 125579
},
{
level: 2,
name: "word10",
id: 125578
},
{
level: 2,
name: "word4",
id: 172670,
_children: [
{
level: 3,
name: "word13",
id: 172669
},
{
level: 3,
name: "word14",
id: 174777
},
{
level: 3,
name: "word5",
id: 207661,
_children: [
{
level: 4,
name: "word15",
id: 216529
},
{
level: 4,
name: "word16",
id: 223884,
_children: [
{
level: 5,
name: "word17",
id: 223885,
_children: [
{
level: 6,
name: "word18",
id: 229186,
_children: [
{
level: 7,
name: "word19",
id: 219062
},
{
level: 7,
name: "word20",
id: 222243
}
]
}
]
}
]
}
]
}
]
},
{
level: 2,
name: "word3",
id: 214266,
_children: [
{
level: 3,
name: "word11",
id: 216675
},
{
level: 3,
name: "word12",
id: 216671
}
]
}
]
}
]
}
];
const idList = [214659];
const found = _.filterDeep(
data,
function(value) {
return _.indexOf(idList, value.id) !== -1;
},
{ tree: true, childrenPath: '_children' }
);
console.log(found);
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/deepdash/browser/deepdash.min.js"></script>
<script>
deepdash(_);
</script>
Here is a recursive function that will find the parent and/or children matching a condition.
In this example, the parent item will always be displayed if a child item is a match - even if the parent itself is not a match - but you can easily adjust the code to your needs by tuning the test in the for loop.
var filterTree = function (data, filter) {
if (data['_children'] && data['_children'].length > 0) {
for (var i in data['_children']) {
return data[filter.field] == filter.value || filterTree(data['_children'][i], filter);
}
}
return data[filter.field] == filter.value;
};
Call this function as a custom filter callback:
table.setFilter(filterTree, {field:'myfield', type:'=', value:'myvalue'});
Note that this is just example code that focuses on the logic of filtering a tree recursively. The above works only for the '=' comparison.
In a real situation, you will have to implement more code to handle all other operators supported by tabulator, as dynamic operator assignment is not possible in Javascript. You could maybe consider eval() but that's another story.
More info about dynamic operator assignment here:
Are Variable Operators Possible?
Here is an example of implementation handling all tabulator operators:
// Operators
var compare = {
'=': function(a, b) { return a == b },
'<': function(a, b) { return a < b },
'<=': function(a, b) { return a <= b },
'>': function(a, b) { return a > b },
'>=': function(a, b) { return a >= b },
'!=': function(a, b) { return a != b },
'like': function(a, b) { return a.includes(b)}
};
// Filter function
var filterTree = function (data, filter) {
if (data['_children'] && data['_children'].length > 0) {
for (var i in data['_children']) {
return compare[filter.type](data[filter.field], filter.value) || filterTree(data['_children'][i], filter);
}
}
return compare[filter.type](data[filter.field], filter.value);
};
// Set a filter. The operator can now be provided dynamically
table.setFilter(filterTree, {field:'myfield', type: '>=', value:'myvalue'});