Terraform deployment for 'Work pace based Application Insight' on Azure - azure

I have been trying to figure out a way to prepare a terraform template for my app service/az function where I can connect it to application Insight while creating them through Terraform. Well the it worked, BUT the application Insight shows
Migrate this resource to Workspace-based Application Insights to gain support for all of the capabilities of Log Analytics, including Customer-Managed Keys and Commitment Tiers. Click here to learn more and migrate in a few clicks.
How do I acheive it from terraform? As from the documentation page of terraform there is no mention of such setup. Appreciate you help on this.
Here is the terraform code for az-function
resource "azurerm_linux_function_app" "t_funcapp" {
name = "t-function-app"
location = local.resource_location
resource_group_name = local.resource_group_name
service_plan_id = azurerm_service_plan.t_app_service_plan.id
storage_account_name = azurerm_storage_account.t_funcstorage.name
storage_account_access_key = azurerm_storage_account.t_funcstorage.primary_access_key
site_config {
application_stack {
java_version = "11"
}
remote_debugging_enabled = false
ftps_state = "AllAllowed"
}
app_settings = {
APPINSIGHTS_INSTRUMENTATIONKEY = "${azurerm_application_insights.t_appinsights.instrumentation_key}"
}
depends_on = [
azurerm_resource_group.t_rg,
azurerm_service_plan.t_app_service_plan,
azurerm_storage_account.t_funcstorage,
azurerm_application_insights.t_appinsights
]
}
Here is the terraform code for app insight
resource "azurerm_application_insights" "t_appinsights" {
name = "t-appinsights"
location = local.resource_location
resource_group_name = local.resource_group_name
application_type = "web"
depends_on = [
azurerm_log_analytics_workspace.t_workspace
]
}
output "instrumentation_key" {
value = azurerm_application_insights.t_appinsights.instrumentation_key
}
output "app_id" {
value = azurerm_application_insights.t_appinsights.app_id
}

You must create a Log Analytics Workspace and add it to your Application Insights.
For example
resource "azurerm_log_analytics_workspace" "example" {
name = "workspace-test"
location = local.resource_location
resource_group_name = local.resource_group_name
sku = "PerGB2018"
retention_in_days = 30
}
resource "azurerm_application_insights" "t_appinsights" {
name = "t-appinsights"
location = local.resource_location
resource_group_name = local.resource_group_name
workspace_id = azurerm_log_analytics_workspace.example.id
application_type = "web"
}
output "instrumentation_key" {
value = azurerm_application_insights.t_appinsights.instrumentation_key
}
output "app_id" {
value = azurerm_application_insights.t_appinsights.app_id
}
Hope this helps!

Related

Terraform Azurerm: Create blob if not exists

I got Terrafrom code that creates storage account, container and block blob. Is it possible to configure that block blob is created only if it doesn't already exist?
In case of re-running terraform I wouldn't like to replace blob if it is already there as the content might have been manually modified and i would like to keep it.
Any tips? Only alternative I could think of is running powershell/bash script during further deployment steps that would create file if needed, but I am curious if this can be done just with Terraform.
locals {
storage_account_name_teast = format("%s%s", local.main_pw_prefix_short, "teast")
}
resource "azurerm_storage_account" "teaststorage" {
name = local.storage_account_name_teast
resource_group_name = azurerm_resource_group.main.name
location = var.location
account_tier = var.account_tier
account_replication_type = var.account_replication_type
allow_nested_items_to_be_public = false
min_tls_version = "TLS1_2"
network_rules {
default_action = "Deny"
bypass = [
"AzureServices"
]
virtual_network_subnet_ids = []
ip_rules = local.ip_rules
}
tags = var.tags
}
resource "azurerm_storage_container" "teastconfig" {
name = "config"
storage_account_name = azurerm_storage_account.teaststorage.name
container_access_type = "private"
}
resource "azurerm_storage_blob" "teastfeaturetoggle" {
name = "featureToggles.json"
storage_account_name = azurerm_storage_account.teaststorage.name
storage_container_name = azurerm_storage_container.teastconfig.name
type = "Block"
source = "vars-pr-default-toggles.json"
}
After scanning through terraform plan I figured out it was forcing a blob replacement because of:
content_md5 = "9a95db04fb1ff3abcd7ff81fcfb96307" -> null # forces replacement
I added lifecycle hook to blob resource to prevent it:
resource "azurerm_storage_blob" "teastfeaturetoggle" {
name = "featureToggles.json"
storage_account_name = azurerm_storage_account.teaststorage.name
storage_container_name = azurerm_storage_container.teastconfig.name
type = "Block"
source = "vars-pr-default-toggles.json"
lifecycle {
ignore_changes = [
content_md5,
]
}
}

Update exsiting Azure App Service in Terraform

I would like to update my exsiting Azure App Service in Terraform by adding a Backup to this App Service.
For now it looks like this:
data "azurerm_app_service_plan" "example" {
name = "MyUniqueServicePlan"
resource_group_name = "example-resources"
}
resource "azurerm_app_service" "example" {
name = "MyUniqueWebAppName"
location = "West Europe"
resource_group_name = "example-resources"
app_service_plan_id = data.azurerm_app_service_plan.example.id
connection_string {
name = "myConectionString"
type = "SQLServer"
value = "Server=tcp:mysqlservername123.database.windows.net,1433;Initial Catalog=MyDatabaseName;Persist Security Info=False;User ID=xxx;Password=xxxxxx;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}
backup {
name = "MyBackupName"
enabled = true
storage_account_url = "https://storageaccountnameqwetih.blob.core.windows.net/mycontainer?sp=r&st=2022-08-31T09:49:17Z&se=2022-08-31T17:49:17Z&spr=https&sv=2021-06-08&sr=c&sig=2JwQ%xx%2B%2xxB5xxxxFZxxVyAadjxxV8%3D"
schedule {
frequency_interval = 30
frequency_unit = "Day"
keep_at_least_one_backup = true
retention_period_in_days = 10
start_time = "2022-08-31T07:11:56.52Z"
}
}
}
But when I run it i got a error A resource with the ID ........ /MyUniqueWebAppName" already exists - to be managed via Terraform this resource needs to be imported into the State.
How in terraform can I point to an existing Azure APP Service and add a backup with the same schedule as I did in my template?
Before you can modify your existing resources with TF, you must import into the terraform state. For this you use import command.
data "azurerm_resource_group" "example" {
name = "<give rg name existing one>"
}
data "azurerm_app_service_plan" "example" {
name = "MyUniqueServicePlan"
resource_group_name = data.azurerm_resource_group.example.name
}
data "azurerm_app_service" "example" {
name = "MyUniqueWebAppName"
location = data.azurerm_resource_group.example.location
resource_group_name = data.azurerm_resource_group.example.name
app_service_plan_id = data.azurerm_app_service_plan.example.id
connection_string {
name = "myConectionString"
type = "SQLServer"
value = "Server=tcp:mysqlservername123.database.windows.net,1433;Initial Catalog=MyDatabaseName;Persist Security Info=False;User ID=xxx;Password=xxxxxx;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}
backup {
name = "MyBackupName"
enabled = true
storage_account_url = "https://storageaccountnameqwetih.blob.core.windows.net/mycontainer?sp=r&st=2022-08-31T09:49:17Z&se=2022-08-31T17:49:17Z&spr=https&sv=2021-06-08&sr=c&sig=2JwQ%xx%2B%2xxB5xxxxFZxxVyAadjxxV8%3D"
schedule {
frequency_interval = 30
frequency_unit = "Day"
keep_at_least_one_backup = true
retention_period_in_days = 10
start_time = "2022-08-31T07:11:56.52Z"
}
}
}
No need to use import command , use this code for your reference
just give rg name existing one in resources group block

Terraform force recreate azure web app when scale up the sku of service plan

i've this terraform code :
# Configure the Azure provider
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 2.65"
}
}
required_version = ">= 0.14.7"
}
provider "azurerm" {
features {}
}
# Generate a random integer to create a globally unique name
resource "random_integer" "ri" {
min = 10000
max = 99999
}
# Create the resource group
resource "azurerm_resource_group" "rg" {
name = "myResourceGroup-${random_integer.ri.result}"
location = "eastus"
}
# Create the Linux App Service Plan
resource "azurerm_app_service_plan" "appserviceplan" {
name = "webapp-asp-${random_integer.ri.result}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
sku {
tier = "Free"
size = "F1"
}
}
# Create the web app, pass in the App Service Plan ID, and deploy code from a public GitHub repo
resource "azurerm_app_service" "webapp" {
name = "webapp-${random_integer.ri.result}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
app_service_plan_id = azurerm_app_service_plan.appserviceplan.id
source_control {
repo_url = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
branch = "master"
manual_integration = true
use_mercurial = false
}
}
This code work as exepected.
now i would try to scale up the SKU of the service plan (to Standard /S1 ), Terraform mark my web app as tainted and say that my web app should be replaced
i try to use the meta argment create_before_destroy in the azure plan definition like this :
resource "azurerm_app_service_plan" "appserviceplan" {
# ...
lifecycle {
create_before_destroy = true
}
}
but it's always ask to recreate the web app
Can someone have a idea about that ?

Terraform 403 error when creating function app and storage account with private endpoint

I am getting a 403 forbidden when creating a function app that connects to its storage account via private endpoint inside a vnet. Storage account has firewall default action of 'Deny', and of course if I set it to 'Allow' it will work. I want this as 'Deny', however. Following this microsoft link if the function app and storage account are created in the same region with vnet, subnets, and private endpoints then it's supposed to work so I must be doing something wrong. I also tried changing the region for the storage account and it still resulted in a 403.
Error:
Error: web.AppsClient#CreateOrUpdate: Failure sending request: StatusCode=0 -- Original Error: Code="BadRequest" Message="There was a conflict. The remote server returned an error: (403) Forbidden." Details=[{"Message":"There was a conflict. The remote server returned an error: (403) Forbidden."},{"Code":"BadRequest"},{"ErrorEntity":{"Code":"BadRequest","ExtendedCode":"01020","Message":"There was a conflict. The remote server returned an error: (403) Forbidden.","MessageTemplate":"There was a conflict. {0}","Parameters":["The remote server returned an error: (403) Forbidden."]}}]
Here is my terraform code
resource "azurerm_function_app" "func" {
name = "${var.func_basics.name}-func"
location = var.func_basics.location
resource_group_name = var.func_basics.resource_group_name
app_service_plan_id = azurerm_app_service_plan.svc_plan.id
storage_account_name = azurerm_storage_account.func_sa.name
storage_account_access_key = azurerm_storage_account.func_sa.primary_access_key
version = var.runtime_version
https_only = true
depends_on = [
azurerm_storage_account.func_sa,
azurerm_app_service_plan.svc_plan,
azurerm_application_insights.func_ai,
azurerm_virtual_network.func_vnet
]
app_settings = merge(var.app_settings, local.additional_app_settings)
}
resource "azurerm_app_service_plan" "svc_plan" {
name = "${var.func_basics.name}-func-plan"
location = var.func_basics.location
resource_group_name = var.func_basics.resource_group_name
kind = "elastic"
sku {
tier = "ElasticPremium"
size = "EP1"
}
}
resource "azurerm_application_insights" "func_ai" {
name = "${var.func_basics.name}-func-appi"
location = var.func_basics.location
resource_group_name = var.func_basics.resource_group_name
application_type = var.ai_app_type
}
resource "azurerm_storage_account" "func_sa" {
name = "st${lower(replace(var.func_basics.name, "/[-_]*/", ""))}"
resource_group_name = var.func_basics.resource_group_name
location = var.func_basics.location
account_tier = var.sa_settings.tier
account_replication_type = var.sa_settings.replication_type
account_kind = "StorageV2"
enable_https_traffic_only = true
min_tls_version = "TLS1_2"
depends_on = [
azurerm_virtual_network.func_vnet
]
network_rules {
default_action = "Deny"
virtual_network_subnet_ids = [azurerm_subnet.func_endpoint_subnet.id]
bypass = [
"Metrics",
"Logging",
"AzureServices"
]
}
}
resource "azurerm_virtual_network" "func_vnet" {
name = "${var.func_basics.name}-func-vnet"
resource_group_name = var.func_basics.resource_group_name
location = var.func_basics.location
address_space = ["10.0.0.0/16"]
}
resource "azurerm_subnet" "func_service_subnet" {
name = "${var.func_basics.name}-func-svc-snet"
resource_group_name = var.func_basics.resource_group_name
virtual_network_name = azurerm_virtual_network.func_vnet.name
address_prefixes = ["10.0.1.0/24"]
enforce_private_link_service_network_policies = true
service_endpoints = ["Microsoft.Storage"]
delegation {
name = "${var.func_basics.name}-func-del"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}
}
resource "azurerm_subnet" "func_endpoint_subnet" {
name = "${var.func_basics.name}-func-end-snet"
resource_group_name = var.func_basics.resource_group_name
virtual_network_name = azurerm_virtual_network.func_vnet.name
address_prefixes = ["10.0.2.0/24"]
enforce_private_link_endpoint_network_policies = true
}
resource "azurerm_private_endpoint" "func_req_sa_blob_endpoint" {
name = "${var.func_basics.name}-func-req-sa-blob-end"
resource_group_name = var.func_basics.resource_group_name
location = var.func_basics.location
subnet_id = azurerm_subnet.func_endpoint_subnet.id
private_service_connection {
name = "${var.func_basics.name}-func-req-sa-blob-pscon"
private_connection_resource_id = azurerm_storage_account.func_sa.id
is_manual_connection = false
subresource_names = ["blob"]
}
}
resource "azurerm_private_endpoint" "func_req_sa_file_endpoint" {
name = "${var.func_basics.name}-func-req-sa-file-end"
resource_group_name = var.func_basics.resource_group_name
location = var.func_basics.location
subnet_id = azurerm_subnet.func_endpoint_subnet.id
private_service_connection {
name = "${var.func_basics.name}-func-req-sa-file-pscon"
private_connection_resource_id = azurerm_storage_account.func_sa.id
is_manual_connection = false
subresource_names = ["file"]
}
}
resource "azurerm_app_service_virtual_network_swift_connection" "func_vnet_swift" {
app_service_id = azurerm_function_app.func.id
subnet_id = azurerm_subnet.func_service_subnet.id
}
locals {
additional_app_settings = {
"APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.func_ai.instrumentation_key
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.func_sa.primary_connection_string
"AzureWebJobsStorage" = azurerm_storage_account.func_sa.primary_connection_string
"WEBSITE_VNET_ROUTE_ALL" = "1"
"WEBSITE_CONTENTOVERVNET" = "1"
"WEBSITE_DNS_SERVER" = "168.63.129.16"
}
}
It seems that it's a common error message when you create an Azure function where the storage account of the function is added to the Virtual Network, read here for more details.
To resolve it, you can use the local-exec Provisioner to invoke the az CLI command to deny the traffic after all of the provisions are finished.
az storage account update --name storage_account_name --resource-group reource_group_name --default-action 'Deny' --bypass 'AzureServices', 'Logging', 'Metrics'
Alternatively, you can separately configure the storage account network rules. You may need to allow your client's IP to access the storage account.
resource "azurerm_storage_account_network_rules" "test" {
resource_group_name = var.resourceGroupName
storage_account_name = azurerm_storage_account.func_sa.name
default_action = "Deny"
bypass = [
"Metrics",
"Logging",
"AzureServices"
]
ip_rules = ["x.x.x.x"]
depends_on = [
azurerm_storage_account.func_sa,
azurerm_app_service_plan.svc_plan,
azurerm_application_insights.func_ai,
azurerm_virtual_network.func_vnet,
azurerm_function_app.func
]
}
In addition, there is a possible solution for this similar case on Github.
I've had this issue in the past and found that it can be resolved as follows. I've tested this on v3.3.0 of the provider using the azurerm_windows_function_app resource. I think currently this is an Azure problem, in that it if you don't supply a share it will try and create one but will be denied. You'd expect this to work if Allow Azure services on the trusted services list to access this storage account is enabled, but webapps aren't trusted.
Create your storage account with IP rules and deny
Create a share within this for your function app content
within the function set the following configuration settings
WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = <storage_account.primary_connection_string>
WEBSITE_CONTENTSHARE = <your share>
WEBSITE_CONTENTOVERVNET = 1
In the functions site configuration set the attribute vnet_route_all_enabled = true

Create main.tf resources only when a variable is set to true in the vars.tf file

I usually have one generic main.tf file that is the basis for all deployments to our environments (DEV/STAGING/LIVE). I have one parameter.tf file for each of those environments.
There is always a requirement to have some more expensive Azure options enabled in the STAGING and LIVE environments over what DEV might have - in my example its enabling the Azure Defender for SQL and extended Auditing functions for Azure SQL servers (PaaS)
This is a portion of my main.tf file that is generic...
# Define SQL Server
resource "azurerm_mssql_server" "example" {
name = var.azsqlserver1name
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
version = var.azsqlserver1version
administrator_login = var.azsqlserver1sauser
administrator_login_password = random_password.sql-password.result
public_network_access_enabled = "true" # set to false with vNet integration
}
# Define Storage Account and container for SQL Threat Detection Policy Audit Logs
resource "azurerm_storage_account" "example" {
name = var.azsaname1
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = var.azsatier1
account_replication_type = var.azsasku1
access_tier = var.azsaaccesstier1
account_kind = var.azsakind1
enable_https_traffic_only = "true"
}
resource "azurerm_storage_container" "example" {
name = "vascans"
storage_account_name = azurerm_storage_account.example.name
container_access_type = "private"
}
# Defines Azure SQL Defender and Auditing - NOTE: Auditing - only SA out at the moment (11/2020) - Log Analytics and Event Hub in preview only
resource "azurerm_mssql_server_security_alert_policy" "example" {
resource_group_name = azurerm_resource_group.example.name
server_name = azurerm_mssql_server.example.name
state = var.azsqltreatdetectionstate
storage_endpoint = azurerm_storage_account.example.primary_blob_endpoint
storage_account_access_key = azurerm_storage_account.example.primary_access_key
email_account_admins = var.azsqltreatdetectionemailadmins
retention_days = var.azsqltreatdetectionretention
}
resource "azurerm_mssql_server_vulnerability_assessment" "example" {
server_security_alert_policy_id = azurerm_mssql_server_security_alert_policy.example.id
storage_container_path = "${azurerm_storage_account.example.primary_blob_endpoint}${azurerm_storage_container.example.name}/"
storage_account_access_key = azurerm_storage_account.example.primary_access_key
recurring_scans {
enabled = var.azsqlvscansrecurring
email_subscription_admins = var.azsqlvscansemailadmins
}
}
resource "azurerm_mssql_server_extended_auditing_policy" "example" {
server_id = azurerm_mssql_server.example.id
storage_endpoint = azurerm_storage_account.example.primary_blob_endpoint
storage_account_access_key = azurerm_storage_account.example.primary_access_key
storage_account_access_key_is_secondary = false
retention_in_days = var.azsqlauditretentiondays
}
What I need to do is have anything after the first "azurerm_mssql_server" resource to only be created in STAGING and LIVE (not DEV). I was planning to have a variable in the DEV/STAGING/LIVE parm tf files that state something like...
DEVparm.tf
variable azsqlenableazuredefenderforsql {
default="false"
}
STAGINGparm.tf and LIVEparm.tf
variable azsqlenableazuredefenderforsql {
default="true"
}
If this possible to achieve? Thus far I've draw a blank and tested a few things, but they don't quite work. It seems a simple enough vision, but when there is no IF... statement
If you need to flip a resource on and off that is easy to achieve with count = 1 or 0. This is usually handled with the ternary operator.
resource "some_resource" "example" {
count = terraform.workspace != "development" ? 1 : 0
}
The count parameter was added to modules for terraform 0.13. If you have a bundle of resources it could be an alterative method to excluding certain resources from building.
One way that a lot of people solve this is by combining the count parameter on resources with a ternary. For example, look at the section entitled "If-Statements with the count parameter" in https://blog.gruntwork.io/terraform-tips-tricks-loops-if-statements-and-gotchas-f739bbae55f9#478c.
Basically you can keep your azsqlenableazuredefenderforsql variable and then in your resources do something like:
resource "azurerm_storage_container" "example" {
count = var.azsqlenableazuredefenderforsql ? 1 : 0
name = "vascans"
storage_account_name = azurerm_storage_account.example.name
container_access_type = "private"
}

Resources