How to create Azure Monitor - Log alerts (with custom KQL query) using Terrafrom - terraform-provider-azure

I have 2 Linux VM in my RG connected to Log-Analytics workspace (Refer below Hierarchy)
Scope-Dev
->Resource-Group-Dev
--->VM-Dev-1
--->VM-Dev-2
I want to create Alert Rule with below options using Terraform ;
Scope : All virtual machines under Resource-Group-Dev
Condition : Log query written in KQL (Pasted below)
dimensions : Computer(Result from KQL query) which i will be using it from action group.
Pref | where TimeGenerated > ago(60m) | where (ObjectName == "Processor") | summarize AggregatedValue = avg(CounterValue) by Computer , _ResourceId | where AggregatedValue < 100 | project Computer, AggregatedValue , _ResourceId

Here i have used azurerm_monitor_scheduled_query_rules_alert_v2 and selected scope as log-analytics-workspace where my VM got connected.As a result it worked.
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "example" {
name = "Resource-Group-Dev"
location = "West Europe"
}
resource "azurerm_log_analytics_workspace" "log_analytics_workspace" {
name = "log-analytics-workspace-custom"
location = "West Europe"
resource_group_name = azurerm_resource_group.example.name
sku = "PerGB2018"
retention_in_days = 30
}
resource "azurerm_monitor_scheduled_query_rules_alert_v2" "alert_v2" {
name = "cpu-alertv2"
resource_group_name = azurerm_resource_group.example.name
location = "West Europe"
evaluation_frequency = "PT5M"
window_duration = "PT5M"
scopes = [azurerm_log_analytics_workspace.log_analytics_workspace.id]
severity = 4
criteria {
query = <<-QUERY
Perf
| where TimeGenerated > ago(1h)
| where CounterName == "% Processor Time" and InstanceName == "_Total"
| project TimeGenerated, Computer, CounterValue, _ResourceId
| summarize AggregatedValue = avg(CounterValue) by bin(TimeGenerated, 1h), Computer, _ResourceId
QUERY
time_aggregation_method = "Maximum"
threshold = 99.0
operator = "LessThan"
resource_id_column = "_ResourceId"
metric_measure_column = "AggregatedValue"
dimension {
name = "Computer"
operator = "Include"
values = ["*"]
}
failing_periods {
minimum_failing_periods_to_trigger_alert = 1
number_of_evaluation_periods = 1
}
}
auto_mitigation_enabled = false
workspace_alerts_storage_enabled = false
description = "This is V2 custom log alert"
display_name = "cpu-alertv2"
enabled = true
query_time_range_override = "P2D"
skip_query_validation = false
action {
action_groups = [azurerm_monitor_action_group.delete_dsvm_action.id]
}
# custom_properties = {}
tags = {
}
}
resource "azurerm_monitor_action_group" "delete_dsvm_action" {
name = "delete-vm-action"
resource_group_name = azurerm_resource_group.example.name
short_name = "destoy-vm"
logic_app_receiver {
name = "auto-deletion-logicapp"
resource_id = azurerm_logic_app_workflow.auto_deletion_logicapp.id
callback_url = azurerm_logic_app_workflow.auto_deletion_logicapp.access_endpoint
use_common_alert_schema = true
}
email_receiver {
name = "sendtoPraveen"
email_address = "kumarpraveen#meta.gov.org"
use_common_alert_schema = true
}
}
resource "azurerm_logic_app_workflow" "auto_deletion_logicapp" {
name = "auto-deletion-logicapp"
location = "East US 2"
resource_group_name = azurerm_resource_group.example.name
}
variable "prefix" {
default = "tfvmex"
}
resource "azurerm_virtual_network" "main" {
name = "${var.prefix}-network"
address_space = ["10.2.0.0/16"]
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
}
resource "azurerm_subnet" "internal" {
name = "internal"
resource_group_name = azurerm_resource_group.example.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.2.2.0/24"]
}
resource "azurerm_network_interface" "main" {
name = "${var.prefix}-nic"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
ip_configuration {
name = "testconfiguration1"
subnet_id = azurerm_subnet.internal.id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_virtual_machine" "main" {
name = "VM-Dev-1"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
network_interface_ids = [azurerm_network_interface.main.id]
vm_size = "Standard_DS1_v2"
storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
storage_os_disk {
name = "myosdisk1"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
os_profile {
computer_name = "hostname"
admin_username = "testadmin"
admin_password = "Password1234!"
}
os_profile_linux_config {
disable_password_authentication = false
}
tags = {
environment = "dev1"
}
}
//VM2
resource "azurerm_virtual_network" "main2" {
name = "${var.prefix}-network2"
address_space = ["10.1.0.0/16"]
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
}
resource "azurerm_subnet" "internal2" {
name = "internal"
resource_group_name = azurerm_resource_group.example.name
virtual_network_name = azurerm_virtual_network.main2.name
address_prefixes = ["10.1.2.0/24"]
}
resource "azurerm_network_interface" "main2" {
name = "${var.prefix}-nic2"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
ip_configuration {
name = "testconfiguration2"
subnet_id = azurerm_subnet.internal2.id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_virtual_machine" "main2" {
name = "VM-Dev-2"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
network_interface_ids = [azurerm_network_interface.main2.id]
vm_size = "Standard_DS1_v2"
storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
storage_os_disk {
name = "myosdisk2"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
os_profile {
computer_name = "hostname1"
admin_username = "testadmin2"
admin_password = "Password123!"
}
os_profile_linux_config {
disable_password_authentication = false
}
tags = {
environment = "dev2"
}
For setting : For setting complex Logic App using Terraform

Replicated the requested change via terraform. Here is a code snippet for adding the KPL query using the Terraform implementation.
**NOTE: The query snippet mentioned is invalid; we can review it on the Azure portal before applying.
Got to Application Insights -> Logs [Monitor] -> Click on any query and validate before implement. **
Step1:
Insert the following code into the main tf file. added a sample query for testing via Terraform.
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "example" {
name = "Resource-Group-Dev"
location = "West Europe"
}
resource "azurerm_application_insights" "example" {
name = "appinsights"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
application_type = "web"
}
resource "azurerm_monitor_scheduled_query_rules_alert" "example" {
name = "examplealert"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
action {
action_group = []
email_subject = "Email Header"
custom_webhook_payload = "{}"
}
data_source_id = azurerm_application_insights.example.id
description = "Alert when total results cross threshold"
enabled = true
query = format(<<-QUERY
let a=requests
| where toint(resultCode) >= 500
| extend fail=1; let b=app('%s').requests
| where toint(resultCode) >= 500 | extend fail=1; a
| join b on fail
QUERY
, azurerm_application_insights.example.id)
severity = 1
frequency = 5
time_window = 30
trigger {
operator = "GreaterThan"
threshold = 3
}
}
variable "prefix" {
default = "tfvmex"
}
resource "azurerm_virtual_network" "main" {
name = "${var.prefix}-network"
address_space = ["10.2.0.0/16"]
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
}
resource "azurerm_subnet" "internal" {
name = "internal"
resource_group_name = azurerm_resource_group.example.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.2.2.0/24"]
}
resource "azurerm_network_interface" "main" {
name = "${var.prefix}-nic"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
ip_configuration {
name = "testconfiguration1"
subnet_id = azurerm_subnet.internal.id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_virtual_machine" "main" {
name = "VM-Dev-1"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
network_interface_ids = [azurerm_network_interface.main.id]
vm_size = "Standard_DS1_v2"
storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
storage_os_disk {
name = "myosdisk1"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
os_profile {
computer_name = "hostname"
admin_username = "testadmin"
admin_password = "Password1234!"
}
os_profile_linux_config {
disable_password_authentication = false
}
tags = {
environment = "dev1"
}
}
//VM2
resource "azurerm_virtual_network" "main2" {
name = "${var.prefix}-network2"
address_space = ["10.1.0.0/16"]
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
}
resource "azurerm_subnet" "internal2" {
name = "internal"
resource_group_name = azurerm_resource_group.example.name
virtual_network_name = azurerm_virtual_network.main2.name
address_prefixes = ["10.1.2.0/24"]
}
resource "azurerm_network_interface" "main2" {
name = "${var.prefix}-nic2"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
ip_configuration {
name = "testconfiguration2"
subnet_id = azurerm_subnet.internal2.id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_virtual_machine" "main2" {
name = "VM-Dev-2"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
network_interface_ids = [azurerm_network_interface.main2.id]
vm_size = "Standard_DS1_v2"
storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
storage_os_disk {
name = "myosdisk2"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
os_profile {
computer_name = "hostname1"
admin_username = "testadmin2"
admin_password = "Password123!"
}
os_profile_linux_config {
disable_password_authentication = false
}
tags = {
environment = "dev2"
}
}
Step2:
Execute below commands
terraform plan
terraform apply -auto-approve
Verification from the portal
Hope this helps!

Related

Attemp to index a nill value

can anybody help me, please. I really don't know what to do... i tried everything,
Attemp to index a nill value (field, ?) at this line
PhoneData.Chats[NumberKey].messages = ChatMessages
RegisterNetEvent('ttpPhone:client:UpdateMessages', function(ChatMessages, SenderNumber, New)
local NumberKey = GetKeyByNumber(SenderNumber)
print(NumberKey)
if New then
PhoneData.Chats[#PhoneData.Chats+1] = {
name = IsNumberInContacts(SenderNumber),
number = SenderNumber,
messages = {},
}
NumberKey = GetKeyByNumber(SenderNumber)
PhoneData.Chats[NumberKey] = {
name = IsNumberInContacts(SenderNumber),
number = SenderNumber,
messages = ChatMessages
}
if PhoneData.Chats[NumberKey].Unread ~= nil then
PhoneData.Chats[NumberKey].Unread = PhoneData.Chats[NumberKey].Unread + 1
print(NumberKey)
else
print(NumberKey)
PhoneData.Chats[NumberKey].Unread = 1
end
if PhoneData.isOpen then
if SenderNumber ~= PhoneData.PlayerData.number then
SendNUIMessage({
action = "PhoneNotification",
PhoneNotify = {
title = "Whatsapp",
text = "New message from "..IsNumberInContacts(SenderNumber).."!",
icon = "fab fa-whatsapp",
color = "#25D366",
timeout = 1500,
},
})
else
SendNUIMessage({
action = "PhoneNotification",
PhoneNotify = {
title = "Whatsapp",
text = "Messaged yourself",
icon = "fab fa-whatsapp",
color = "#25D366",
timeout = 4000,
},
})
end
NumberKey = GetKeyByNumber(SenderNumber)
ReorganizeChats(NumberKey)
print(NumberKey)
Wait(100)
wrPhone.TriggerServerCallback('ttpPhone:server:GetContactPictures', function(Chats)
SendNUIMessage({
action = "UpdateChat",
chatData = Chats[GetKeyByNumber(SenderNumber)],
chatNumber = SenderNumber,
Chats = Chats,
})
end, PhoneData.Chats+1)
else
SendNUIMessage({
action = "PhoneNotification",
PhoneNotify = {
title = "Whatsapp",
text = "New message from "..IsNumberInContacts(SenderNumber).."!",
icon = "fab fa-whatsapp",
color = "#25D366",
timeout = 3500,
},
})
Config.PhoneApplications['whatsapp'].Alerts = Config.PhoneApplications['whatsapp'].Alerts + 1
TriggerServerEvent('ttpPhone:server:SetPhoneAlerts', "whatsapp")
end
else
print(chats)
print(NumberKey)
---------------------------------------------------------------------------->
PhoneData.Chats[NumberKey].messages = ChatMessages
<------------------------------------------------------------------------------
if PhoneData.Chats[NumberKey].Unread ~= nil then
PhoneData.Chats[NumberKey].Unread = PhoneData.Chats[NumberKey].Unread + 1
else
PhoneData.Chats[NumberKey].Unread = 1
end
if PhoneData.isOpen then
if SenderNumber ~= PhoneData.PlayerData.number then
SendNUIMessage({
action = "PhoneNotification",
PhoneNotify = {
title = "Whatsapp",
text = "New message from "..IsNumberInContacts(SenderNumber).."!",
icon = "fab fa-whatsapp",
color = "#25D366",
timeout = 1500,
},
})
else
SendNUIMessage({
action = "PhoneNotification",
PhoneNotify = {
title = "Whatsapp",
text = "Messaged yourself",
icon = "fab fa-whatsapp",
color = "#25D366",
timeout = 4000,
},
})
end
NumberKey = GetKeyByNumber(SenderNumber)
ReorganizeChats(NumberKey)
Wait(100)
wrPhone.TriggerServerCallback('ttpPhone:server:GetContactPictures', function(Chats)
SendNUIMessage({
action = "UpdateChat",
chatData = Chats[GetKeyByNumber(SenderNumber)],
chatNumber = SenderNumber,
Chats = Chats,
})
end, PhoneData.Chats)
else
SendNUIMessage({
action = "PhoneNotification",
PhoneNotify = {
title = "Whatsapp",
text = "New message from "..IsNumberInContacts(SenderNumber).."!",
icon = "fab fa-whatsapp",
color = "#25D366",
timeout = 3500,
},
})
NumberKey = GetKeyByNumber(SenderNumber)
ReorganizeChats(NumberKey)
Config.PhoneApplications['whatsapp'].Alerts = Config.PhoneApplications['whatsapp'].Alerts + 1
TriggerServerEvent('ttpPhone:server:SetPhoneAlerts', "whatsapp")
end
end
end)
Dump the PhoneData table and check if the value exists, it probably doesn't and it's giving you nil.

Terraform virtual wan azurerm_vpn_gateway_connection

Looking to create dynamic Azure vwan vpn site connections. I am using this map to define the sites and links. I would like to be able to add and remove sites and links as my requirements change.
virtual_wan_vpn_sites = {
vwan_site_dc = {
name = "site-shr-infra-dc"
location_map_key = "primary"
resource_group_key = "rg_vwan"
vwan_key = "vwan"
device_vendor = "Fortigate"
device_model = "FGT60F"
links = {
link_1 = {
name = "link-shr-infra-dc-1"
bgp_asn = "64512"
public_ip_address = "1.1.1.1"
bgp_peering_ip = "10.10.100.1"
}
link_2 = {
name = "link-shr-infra-dc-2"
bgp_asn = "64513"
public_ip_address = "2.2.2.2"
bgp_peering_ip = "10.10.100.100"
}
}
}
}
and this code to create the sites, links, and connections.
# Create vpn site(s)
module "virtualWanVpnSites" {
source = "../../modules/networking/virtual_wan_vpn_site"
for_each = var.virtual_wan_vpn_sites
name = each.value.name
location = var.location_map[each.value.location_map_key]
resource_group_name = azurerm_resource_group.resource_group[each.value.resource_group_key].name
virtual_wan_id = module.virtualWan[each.value.vwan_key].virtual_wan_id
vwan_key = each.value.vwan_key
vwan_sites = each.value.links
device_vendor = each.value.device_vendor
device_model = each.value.device_model
vpn_gateways = values(module.virtualHubVpn)[*].virtual_hub_vpn_gateway_id
tags = merge(lookup(each.value, "tags", {}), local.tags)
../../modules/networking/virtual_wan_vpn_site
# Virtual Wan vpn site
resource "azurerm_vpn_site" "vwan_vpn_site" {
name = var.name
location = var.location
resource_group_name = var.resource_group_name
virtual_wan_id = var.virtual_wan_id
device_vendor = var.device_vendor
device_model = var.device_model
tags = local.tags
dynamic "link" {
for_each = try(var.vwan_sites, {})
content {
name = link.value.name
ip_address = link.value.public_ip_address
bgp {
asn = link.value.bgp_asn
peering_address = link.value.bgp_peering_ip
}
}
}
}
# vhub vpn gateway connection
resource "azurerm_vpn_gateway_connection" "vhub_vpn_gateway_connection" {
for_each = toset(var.vpn_gateways)
name = "example"
vpn_gateway_id = each.key
remote_vpn_site_id = azurerm_vpn_site.vwan_vpn_site.id
*dynamic "vpn_link" {
for_each = try(azurerm_vpn_site.vwan_vpn_site.link, [])
content {
name = vpn_link.value.name
vpn_site_link_id = vpn_link.value.id
bgp_enabled = true
}
}*
}
The vwan site and links get created successfully, however, the link connections error out with this:
Error: Missing required argument
│
│ with module.virtualWanVpnSites["vwan_site_dc"].azurerm_vpn_gateway_connection.vhub_vpn_gateway_connection["/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/vpnGateways/vpn-shr-infra-usce"],
│ on ../../modules/networking/virtual_wan_vpn_site/module.tf line 25, in resource "azurerm_vpn_gateway_connection" "vhub_vpn_gateway_connection":
│ 25: resource "azurerm_vpn_gateway_connection" "vhub_vpn_gateway_connection" {
│
│ The argument "vpn_link.1.vpn_site_link_id" is required, but no definition
│ was found.
╵
╷
│ Error: Missing required argument
│
│ with module.virtualWanVpnSites["vwan_site_dc"].azurerm_vpn_gateway_connection.vhub_vpn_gateway_connection["/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/vpnGateways/vpn-shr-infra-use2"],
│ on ../../modules/networking/virtual_wan_vpn_site/module.tf line 25, in resource "azurerm_vpn_gateway_connection" "vhub_vpn_gateway_connection":
│ 25: resource "azurerm_vpn_gateway_connection" "vhub_vpn_gateway_connection" {
│
│ The argument "vpn_link.1.vpn_site_link_id" is required, but no definition
│ was found.
It appears that the resource azurerm_vpn_gateway_connection wants the vpn_site_link_id like this vpn_site_link_id = azurerm_vpn_site.example.link[0].id.
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/vpn_gateway_connection
I can see the links in the state file like this:
"module": "module.virtualWanVpnSites[\"vwan_site_dc\"]",
"mode": "managed",
"type": "azurerm_vpn_site",
"name": "vwan_vpn_site",
"provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"address_cidrs": [],
"device_model": "FGT60F",
"device_vendor": "Fortigate",
"id": "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/vpnSites/site-shr-infra-dc",
"link": [
{
"bgp": [
{
"asn": 64512,
"peering_address": "10.10.100.1"
}
],
"fqdn": "",
"id": "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/vpnSites/site-shr-infra-dc/vpnSiteLinks/link-shr-infra-dc-1",
"ip_address": "1.1.1.1",
"name": "link-shr-infra-dc-1",
"provider_name": "",
"speed_in_mbps": 0
},
{
"bgp": [
{
"asn": 64513,
"peering_address": "10.10.100.100"
}
],
"fqdn": "",
"id": "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/vpnSites/site-shr-infra-dc/vpnSiteLinks/link-shr-infra-dc-2",
"ip_address": "2.2.2.2",
"name": "link-shr-infra-dc-2",
"provider_name": "",
"speed_in_mbps": 0
}
],
Any ideas would be greatly appreciated. Thanks.
Please check this terraform-azurerm-virtual-wan · GitHub
Initially , check if the version is the issue and see with v2.78.0 version of the Terraform Provider.
Try also setting address_cidrs to create an address space
I have used address_cidrs = ["10.0.0.0/16"] for azurerm_vpn_site
And address_prefix = "10.0.0.0/24" in azurerm_virtual_hub.
vpn_sites = [
{
name = "site1"
links = [
{
name = "site1-primary-endpoint"
ip_address = "20.20.20.20"
bgp = [
{
asn = 65530
peering_address = "169.254.21.2"
}
]
},
{
name = "site1-secondary-endpoint"
ip_address = "21.21.21.21"
bgp = [
{
asn = 65530
peering_address = "169.254.22.2"
}
]
}
]
}
]
As you said vpn_link is expected in the format vpn_site_link_id = vpn_link.value[0].id
Please also check the Reference : Terraform for-each with list of objects - Stack Overflow

Create multiple postgres objects using Terraform and for_each meta argument

I would like to create several postgres users, databases and passwords and output them with terraform. As of now, what I'm doing is this (for one of the application database):
main.tf
resource "random_password" "secret_password_1" {
length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
resource "postgresql_role" "application_db_role_1" {
name = "db_user_1"
login = true
password = random_password.secret_password_1.result
encrypted_password = true
}
resource "postgresql_database" "application_db_1" {
name = "db_name_1"
owner = postgresql_role.application_db_role_1.name
allow_connections = true
}
outputs.tf
output "db_name_1" {
value = postgresql_database.application_db_1.name
}
output "db_user_1" {
value = postgresql_role.application_db_role_1.name
}
output "db_1_password" {
value = random_password.secret_password_1.result
sensitive = true
}
Would it be possible to use for_each meta argument to make this code simpler when having several databases, starting from a list of DB names such as:
locals {
list_of_databases = ["db_1", "db_2"]
}
Finally found a clean solution:
main.tf
locals {
list_of_databases = ["one_database_example"]
}
resource "random_password" "password" {
for_each = toset(local.list_of_databases)
length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
resource "postgresql_role" "application_db_role" {
for_each = toset(local.list_of_databases)
name = "${each.value}_user"
login = true
password = random_password.password[each.value].result
encrypted_password = true
}
resource "postgresql_database" "application_db" {
for_each = toset(local.list_of_databases)
name = "${each.value}_db"
owner = postgresql_role.application_db_role[each.value].name
allow_connections = true
}
And outputs
output "shared_db_application_db_names" {
value = {
for v in local.list_of_databases : v => postgresql_database.application_db[v].name
}
}
output "shared_db_application_db_roles" {
value = {
for v in local.list_of_databases : v => postgresql_role.application_db_role[v].name
}
}
output "shared_db_application_db_passwords" {
value = {
for v in local.list_of_databases : v => random_password.password[v].result
}
sensitive = true
}

for_each for aws_iam_policy data block - not working (dynamic code inside loop)

I am using terraform 0.14
Here is a working version of my code:
#MODULE DEFINITION (in a folder named "iam-group-with-policies")
resource "aws_iam_policy" "custom" {
count = length(var.custom_group_policies)
name = var.custom_group_policies[count.index]["name"]
policy = var.custom_group_policies[count.index]["policy"]
description = lookup(var.custom_group_policies[count.index], "description", null)
tags = var.tags
}
#RESOURCE DEFINITION
module "iam_group_with_custom_policies_S3" {
source = "../modules/iam-group-with-policies"
name = "S3_viewer"
group_users = ["user8", "user9"]
custom_group_policies = [
{
name = "Custom_S3viewer"
policy = data.aws_iam_policy_document.sample.json
},
]
}
#DATA BLOCK
data "aws_iam_policy_document" "sample" {
statement {
actions = [
"s3:ListBuckets",
]
resources = ["*"]
}
}
using that data block defined above is mentioned here - https://learn.hashicorp.com/tutorials/terraform/aws-iam-policy?in=terraform/aws#refactor-your-policy
It is working perfectly fine, I can have multiple resource definitions pointing to different data blocks.
Now I wanted to extend this code to add more variables to it to support multiple policy creation using single block of resource, so I thought of using for_each:
#MODULE DEFINITION
resource "aws_iam_policy" "custom" {
count = length(var.custom_group_policies)
name = var.custom_group_policies[count.index]["name"]
policy = var.custom_group_policies[count.index]["policy"]
description = lookup(var.custom_group_policies[count.index], "description", null)
tags = var.tags
}
resource "aws_iam_group" "this" {
count = var.create_group ? 1 : 0
name = var.name
}
resource "aws_iam_group_membership" "this" {
count = length(var.group_users) > 0 ? 1 : 0
group = local.group_name
name = var.name
users = var.group_users
}
################################
# IAM group policy attachements
################################
resource "aws_iam_group_policy_attachment" "custom_arns" {
count = length(var.custom_group_policy_arns)
group = local.group_name
policy_arn = element(var.custom_group_policy_arns, count.index)
}
resource "aws_iam_group_policy_attachment" "custom" {
count = length(var.custom_group_policies)
group = local.group_name
policy_arn = element(aws_iam_policy.custom.*.arn, count.index)
}
#RESOURCE DEFINITION
module "iam_group_with_custom_policies_looptest" {
source = "../modules/iam-group-with-policies"
for_each = var.user_groups
name = each.key
group_users = each.value.user_list
custom_group_policies = [
{
name = each.value.policy_name
policy = each.value.policy
# policy = lookup(var.user_groups_policies, each.key, [""])
# policy = {trim(each.value.policy, "\"")}
},
]
}
#VARIABLE DEFINITION
variable "user_groups" {
description = "Map of user groups and associated custom policies"
type = map(any)
default = {
EB_viewer = {
policy_name = "Custom_EB"
user_list = ["user7", "user9"]
policy = data.aws_iam_policy_document.custom_eb.json
},
EC2_viewer = {
policy_name = "Custom_EC2viewer"
user_list = ["user8", "user9"]
policy = data.aws_iam_policy_document.custom_ec2viewer.json
}
}
}
#DATA BLOCK
data "aws_iam_policy_document" "custom_ec2viewer" {
statement {
actions = [
"ec2:DescribeFastLaunchImages",
"ec2:DescribeInstances"
"ec2:DescribeConversionTasks"
]
resources = ["*"]
}
}
data "aws_iam_policy_document" "custom_eb" {
statement {
actions = [
"elasticbeanstalk:DescribePlatformVersion",
"elasticbeanstalk:DescribeAccountAttributes",
"elasticbeanstalk:RetrieveEnvironmentInfo"
]
resources = ["*"]
}
}
but this is failing with following error:
Error: "policy" contains an invalid JSON policy
on ../modules/iam-group-with-policies/main.tf line 45, in resource "aws_iam_policy" "custom":
45: policy = var.custom_group_policies[count.index]["policy"]
Error: "policy" contains an invalid JSON policy
on ../modules/iam-group-with-policies/main.tf line 45, in resource "aws_iam_policy" "custom":
45: policy = var.custom_group_policies[count.index]["policy"]
I feel the problem is with the line in RESOURCE BLOCK where I mention:
policy = each.value.policy
here it is considering the literal string that will come out as a value of "each.value.policy" i.e. "data.aws_iam_policy_document.custom_eb.json" OR "data.aws_iam_policy_document.custom_ec2viewer.json". but I want it to execute this and get the value that resides in that data block.
To all the terraform gods out there, any suggestions on how to achieve this?

Module: S3 bucket for_each with different policy on each

I am trying to see if this is possible
Terraform 0.12.28
bucket_names = {
"bucket1" = "test_temp"
"bucket2" = "test_jump"
"bucket3" = "test_run"
}
module "s3" {
source = "./modules/s3"
name = var.bucket_names
region = var.region
tags = var.tags
}
module
resource "aws_s3_bucket" "s3" {
for_each = var.name
bucket = "${each.value}"
region = var.region
request_payer = "BucketOwner"
tags = var.tags
versioning {
enabled = false
mfa_delete = false
}
}
Works JUST fine, each bucket is created. BUT now my question is how can i keep a clean way of applying specific policies to each bucket in the list?
policy 1 => test1_temp
policy 2 => test2_jump
policy 3 => test2_run
Each policy will be slightly different
My thoughts, regex to find _temp and apply policy1 etc
I'm with #ydaetkocR on this. It's complexity for no gain in a real system, but it could be interesting for learning.
terraform.tfvars
bucket_names = {
"bucket1" = "test_temp"
"bucket2" = "test_jump"
"bucket3" = "test_run"
}
bucket_policy = {
"bucket1" = <<POLICY
{
"Version": "2012-10-17",
"Id": "MYBUCKETPOLICY",
"Statement": [
{
"Sid": "IPAllow",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::test_temp/*",
"Condition": {
"IpAddress": {"aws:SourceIp": "8.8.8.8/32"}
}
}
]
}
POLICY
"bucket2" = "..."
"bucket3" = "..."
}
root module
module "s3" {
source = "./modules/s3"
name = var.bucket_names
policy = var.bucket_policies
region = var.region
tags = var.tags
}
modules/s3
resource "aws_s3_bucket" "s3" {
for_each = var.name
bucket = each.value
region = var.region
request_payer = "BucketOwner"
tags = var.tags
versioning {
enabled = false
mfa_delete = false
}
}
resource "aws_s3_bucket_policy" "s3" {
for_each = var.policy
bucket = aws_s3_bucket.s3[each.key].id
policy = each.value
}
The trick here is having the same keys for names, policies, and resource instances. You don't have to use two maps, but it's likely the simplest example.
As you can see above it would be a nuisance doing this because you'd have to manually synch the bucket names in the policies or write some very elaborate code to substitute the values in your module.

Resources