Setup "monthDays" property in case of trigger recurrence Logic App with terraform - terraform

I would like to define with terraform code a logic app trigger definition so that the trigger runs only once on the last day of each month.
I have found the following code snippet on related Microsoft site.
"triggers": {
"Recurrence": {
"recurrence": {
"frequency": "Month",
"interval": 1,
"schedule": {
"monthDays": [-1]
}
},
"type": "Recurrence"
}
}
If I type it manually into Logic app recurrent step, Its working without error.
It should look like this code snippet but I cannot find the equivalent in terraform.
resource "azurerm_logic_app_trigger_recurrence" "trigger_recurrence" {
name = "run_only_last_day"
logic_app_id = azurerm_logic_app_workflow.example.id
frequency = "Month"
interval = 1
start_time = var.start_time
time_zone = var.time_zone
dynamic "schedule" {
for_each = var.use_advanced_scheduling ? [true] : []
content {
on_these_days = var.on_these_days
at_these_hours = var.at_these_hours
at_these_minutes = var.at_these_minutes
}
}
I have tried to set on_these_days property because "weekDays": [] is generated by this property but this also throw error.
Error: expected schedule.0.on_these_days.0 to be one of [Monday Tuesday Wednesday Thursday Friday Saturday Sunday], got -1
I also know that start_time should be the last day of next month, but I have concerns about following runs because it doesn't guarantee that it will trigger only the last day of the current month. (details)
Is it possible that monthDays property has been forgotten to be implemented for azurerm provider by Terraform hashicorp dev team? I didn't find this property in schedule section in the documentation.
UPDATE: As I suspected, the monthDays property hasn't been implemented by azurerm_logic_app_trigger_recurrence yet.
Here is an example workaround until monthDays is ready to use.
resource "azurerm_logic_app_trigger_custom" "conditional_trigger_recurrence" {
name = "conditional_custom_recurrent_trigger"
logic_app_id = azurerm_logic_app_workflow.example.id
body = jsonencode({
conditions = var.condition_expressions,
recurrence = merge({
frequency = var.frequency
interval = var.interval
},
coalesce(var.use_advanced_scheduling ? {
schedule = {
hours = var.at_these_hours
minutes = var.at_these_minutes
monthDays = [-1]
}
} : null, {}),
coalesce(var.start_time != null ? {
startTime = var.start_time
} : null, {}),
coalesce(var.time_zone != null ? {
timeZone = var.time_zone
} : null, {}))
type = "Recurrence"})
Lets use azurerm_logic_app_trigger_custom instead of azurerm_logic_app_trigger_recurrence.
There are two possible solutions available:
Add monthDays = [-1] to azurerm_logic_app_trigger_custom
Add Trigger condition to recurrent logic app action.
The provided template contains both approachs.
Some extra comments to second approach:
Body of azurerm_logic_app_trigger_custom can be customized. In this way, You can add Trigger condition to your recurrent based Logic App.
There are multiple way to check the current day is the same as the day you are looking for (in our case, this is the last day of the current month.)
Here is an example to check that today is the last day of the month with logic app built-in functions:
condition_expressions = [{
expression = "#equals(dayOfMonth(utcNow()), dayOfMonth(addDays(startOfMonth(addDays(startOfMonth(utcNow()),31)),-1)))"
}]
The result is the following:

Yes, the months property is currently unavailable in azurerm_logic_app_trigger_recurrence. And start time should be the last day of the coming month as you mentioned.
Set on_these_days = ["LastDayOfMonth"].
So, if you set the on_these_days property to ["LastDayOfMonth"], then it will only trigger on the last day of the current month.
I made a few modifications to your dynamic block and was able to deploy successfully.
dynamic "schedule" {
for_each = var.use_advanced_scheduling ? [true] : []
content {
on_these_days = ["LastDayOfMonth"]
at_these_hours = ["8","9"]
at_these_minutes = [0,15,30...]
}
I tried sample script with a week frequency including a dynamic schedule block for your reference and it worked as shown:
resource "azurerm_logic_app_workflow" "example" {
name = "<workflow>"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
}
provider "azurerm"{
features{}
}
resource "azurerm_logic_app_trigger_recurrence" "trigger_recurrence" {
name = "xxxx"
logic_app_id = azurerm_logic_app_workflow.example.id
frequency = "Week"
interval = 1
start_time = "2023-03-01T00:00:00Z"
dynamic "schedule" {
for_each = var.use_advanced_scheduling ? [true] : []
content {
on_these_days = ["Monday","Tuesday"]
at_these_hours = ["8","9"]
at_these_minutes = [0,15,30]
}
}
}
terraform init:
terraform plan:
terraform apply:
Deployed in Azure Portal:

Related

Create GCP alerting policy for uptime check using terraform

Is there any way to create GCP alerting policy for uptime check using terraform and filter value of metric.label.check_id of already deployed resource?
Provided examples in the terraform docs show only alerting policy for metrics not for uptime check for already deployed resource so I’m not sure if that is even possible with the terraform.
I have figure out a solution which works in my case.
I have create uptime check and uptime check alert by two separate terraform modules.
Terrraform uptime check module looks like:
resource "google_monitoring_uptime_check_config" "uptime-check" {
project = var.project_id
display_name = var.display_name
timeout = "10s"
period = "60s"
http_check {
path = var.path
port = var.port
use_ssl = true
validate_ssl = true
}
monitored_resource {
type = "uptime_url"
labels = {
host = var.hostname,
project_id = var.project_id
}
}
content_matchers {
content = "\"status\":\"UP\""
}
}
Then for the outputs.tf for that module I have:
output "uptime_check_id" {
value = google_monitoring_uptime_check_config.uptime-check.uptime_check_id
}
Then in the alerts module I have follow terraform docs but modified them to code which looks like:
module "medallies-common-alerts" {
source = "./modules/alerts"
project_id = var.project_id
uptime_check_depends_on = [module.uptime-check]
check_id = module.uptime-check.uptime_check_id
}
...
resource "google_monitoring_alert_policy" "alert_policy_uptime_check" {
project = var.project_id
enabled = true
depends_on = [var.uptime_check_depends_on]
....
condition_threshold {
filter = format("metric.type=\"monitoring.googleapis.com/uptime_check/check_passed\" AND metric.label.\"check_id\"=\"%s\" AND resource.type=\"uptime_url\"",var.check_id)
duration = "300s"
comparison = "COMPARISON_GT"
threshold_value = "1"
trigger {
count = 1
}
...
}
Hope it will help someone too.

Stackdriver "Alert & Notification" in GCP using Terraform

I am unable to set Alert & Notification in Stackdriver. Able to configure Notification channel and Uptime Check using Terraform. Below are the codes I am using.
resource "google_monitoring_notification_channel" "basic" {
display_name = "Test Notification Channel"
type = "email"
labels = {
email_address = "fakeid007#gmail.com"
}
project = "department1"
}
resource "google_monitoring_uptime_check_config" "http" {
display_name = "01 - Website uptime check [global]"
timeout = "10s"
period = "60s"
project = "department1"
http_check {
path = "/"
port = "80"
mask_headers = null
use_ssl = null
validate_ssl = null
request_method = "GET"
}
monitored_resource {
type = "uptime_url"
labels = {
host = "35.184.98.16"
}
}
}
But I am not able to figure out the below part, which is possible with manual intervention.
In the picture you can see that the notification channel I created with terraform is visible.
Request to help me automate the Alert & Notification also.
I couldn't find a way of doing this through TF, gcloud or the API.
In the end I used the google_monitoring_alert_policy using a Monitoring Query Language query with the condition_monitoring_query_language block, along the lines of:
resource "google_monitoring_alert_policy" "some-uptime-monitor-policy" {
display_name = "Some Uptime Monitor"
conditions {
display_name = "Some Site Unavailable"
condition_monitoring_query_language {
query = "fetch uptime_url | metric 'monitoring.googleapis.com/uptime_check/check_passed' | filter (resource.host == 'somesite.com') | group_by 1m, [value_check_passed_count_true: count_true(value.check_passed)] | every 5m | condition value_check_passed_count_true < 1 '1'"
duration = "60s"
}
}
}
I had several monitors which is why I went the condition_monitoring_query_language way, if you have one monitor you could use condition_threshold in its place. I found I couldn't get what I wanted with condition_threshold due the multi site requirement

How to skip/ignore specific module in terraform if var is null or empty

Is there any way to skip the terraform block/file if defined variable is empty or null. Instead of throwing error.
I have created tow terraform script for azure.
For azure automation creation, runbook creation.
For event grid creation.
After executing the first step I have to generate a webhook URL manually (there no such automation support for webhook generation). after generating of webhook URL I need to define it in second resource. If I defined empty/null or invalid URL then terraform throw the error.
below is the terraform code.
data "local_file" "runbook_script" {
filename = "${path.module}/envent-grid-runbook.ps1"
}
resource "azurerm_automation_runbook" "runbook" {
name = "event-gird-notification"
location = var.location
resource_group_name = var.resource_group_name
automation_account_name = azurerm_automation_account.CreateAutomation.name
log_verbose = true
log_progress = true
description = "This runbook is creted for event grid notification"
runbook_type = "PowerShell"
content = data.local_file.runbook_script.content
publish_content_link {
uri = ""
}
}
resource "azurerm_eventgrid_event_subscription" "key-vault" {
name = "test"
scope = "/subscriptions/xxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxx/name"
topic_name = "/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxx/vault"
webhook_endpoint {
url = var.webhook_url
}
included_event_types = [
"Microsoft.KeyVault.SecretNewVersionCreated",
"Microsoft.KeyVault.SecretNearExpiry",
"Microsoft.KeyVault.SecretExpired"
]
event_delivery_schema = "EventGridSchema"
}
If I defined null/empty var for webhook ULR variable. then getting below error.
Error: "webhook_endpoint.0.url": required field is not set
I have created a Jenkins job, where all terraform code to run in a single job. if the code fails, then entire job is getting failed. That's why looking for a solution to skip the specific block/file if var is empty or null.
I assume that you want to make entire azurerm_eventgrid_event_subscription resource optional, based on var.webhook_url you can use count.
For example:
resource "azurerm_eventgrid_event_subscription" "key-vault" {
count = var.webhook_url == "" ? 0 : 1
name = "test"
scope = "/subscriptions/xxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxx/name"
topic_name = "/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxx/vault"
webhook_endpoint" {
url = var.webhook_url
}
included_event_types = [
"Microsoft.KeyVault.SecretNewVersionCreated",
"Microsoft.KeyVault.SecretNearExpiry",
"Microsoft.KeyVault.SecretExpired"
]
event_delivery_schema = "EventGridSchema"
}
In the above example, you may need to adjust the condition based on what values var.webhook_url can actually have to be consider correct or incorrect.

Terraform azurerm schedule start_time always resets on new deploys

I am trying to get the resource azurerm_automation_schedule to deploy at a specific time (ex: 18:00) occurring monthly.
I'm using the following code:
locals {
update_time = "18:00"
update_date = formatdate("YYYY-MM-DD", timeadd(timestamp(), "24h"))
update_timezone = "UTC"
}
resource "azurerm_automation_schedule" "main" {
name = "test"
resource_group_name = "myresourcegroupname"
automation_account_name = "myautomationaccountname"
frequency = "Month"
timezone = local.update_timezone
start_time = "${local.update_date}T${local.update_time}:00+02:00"
description = "This is an example schedule"
monthly_occurrence {
day = "Tuesday"
occurrence = "1"
}
}
The "${local.update_date}T${local.update_time}:00+02:00" adds 2 hours to the current time and sets the day forward 1. This is required to ensure the schedule starts in the future.
This works fine, except the next time I come back to run a deploy, it detects a new change due to the date changing, even if no real changes have occurred.
The start_time will always tick forward.
I can't seem to find any terraform logic that can assist.
Is there a way to set a static start time in a variable, and only have it updated if it changes? (not the date).
The psuedocode would be:
if [update_time] has not changed, do not update [azurerm_automation_schedule]
else update [azurerm_automation_schedule] with the new time, incrementing the day forward
Update
My final working code (BONUS: With windows update scheduler which is a pain to get working!)
//== Provider used to store timestamp for updates ==//
provider "time" {
version = "~> 0.4"
}
//== Store 1 day in the future, only update if [local.update_time] is altered ==//
resource "time_offset" "next_day" {
offset_days = 1
triggers = {
update_time = local.update_time
}
}
locals {
update_time = "19:40"
update_date = substr(time_offset.next_day.rfc3339, 0, 10)
update_timezone = "UTC"
update_max_hours = "4"
update_classifications = "Critical, Security, UpdateRollup, ServicePack, Definition, Updates"
update_reboot_settings = "IfRequired"
update_day = "Tuesday"
update_occurrence = "2"
}
#This type should eventually replace the manual deploy via azurerm: azurerm_automation_softwareUpdateConfigurations
#https://github.com/terraform-providers/terraform-provider-azurerm/issues/2812
resource "azurerm_template_deployment" "windows" {
name = "windows-update"
resource_group_name = module.stack.azurerm_resource_group.name
template_body = <<DEPLOY
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"apiVersion": "2017-05-15-preview",
"type": "Microsoft.Automation/automationAccounts/softwareUpdateConfigurations",
"name": "${module.stack.azurerm_automation_account.name}/windows-updates",
"properties": {
"updateConfiguration": {
"operatingSystem": "Windows",
"duration": "PT${local.update_max_hours}H",
"windows": {
"excludedKbNumbers": [
],
"includedUpdateClassifications": "${local.update_classifications}",
"rebootSetting": "${local.update_reboot_settings}"
},
"azureVirtualMachines": [
"${module.server_1.azurerm_virtual_machine.id}",
"${module.server_2.azurerm_virtual_machine.id}"
],
"nonAzureComputerNames": [
]
},
"scheduleInfo": {
"frequency": "Month",
"startTime": "${local.update_date}T${local.update_time}:00",
"timeZone": "${local.update_timezone}",
"interval": 1,
"advancedSchedule": {
"monthlyOccurrences": [
{
"occurrence": "${local.update_occurrence}",
"day": "${local.update_day}"
}
]
}
}
}
}
]
}
DEPLOY
deployment_mode = "Incremental"
}
The reason it keeps planning changes is because your code, as written, refers to the current time, rather than getting "tomorrow" and tracking it somehow.
To do that, you need a way to get "tomorrow" once, and stick it in the state. Things that live in the state are resources, so you need a resource that represents a time with an offset. That's where the time provider comes in.
Here's the essential piece:
resource "time_offset" "tomorrow" {
offset_days = 1
}
That will get "tomorrow" for you and after an apply it will be saved in the Terraform state.
time_offset.tomorrow.rfc3339
Will evaluate to something like:
2020-05-13T04:28:07Z
But, we only want the YYYY-MM-DD from that, so we use substr to get the first 10 characters:
substr(time_offset.tomorrow.rfc3339, 0, 10)
Putting it all together, we get this (4 lines added including whitespace, 1 line changed):
locals {
update_time = "18:00"
update_date = substr(time_offset.tomorrow.rfc3339, 0, 10)
update_timezone = "UTC"
}
resource "time_offset" "tomorrow" {
offset_days = 1
}
resource "azurerm_automation_schedule" "main" {
name = "test"
resource_group_name = "myresourcegroupname"
automation_account_name = "myautomationaccountname"
frequency = "Month"
timezone = local.update_timezone
start_time = "${local.update_date}T${local.update_time}:00+02:00"
description = "This is an example schedule"
monthly_occurrence {
day = "Tuesday"
occurrence = "1"
}
}
You may need to bring in the time provider to use it (put this alongside your AzureRM provider if it doesn't work without it):
provider "time" {}
You can use terraform taint 'time_offset.tomorrow' to force the time to be recalculated if you need it to be.
Sharing back. I created a terraform module from the info in this thread to simplify scheduling updates. Work for both linux and windows VMs:
https://github.com/canada-ca-terraform-modules/terraform-azurerm_update_management
Here is an example of how you can use the module to accomplish what you are looking for:
locals {
update_time = "18:00"
update_date = substr(time_offset.tomorrow.rfc3339, 0, 10)
update_timezone = "UTC"
}
resource "time_offset" "tomorrow" {
offset_days = 1
}
module "linux-weekly-updates" {
source = "github.com/canada-ca-terraform-modules/terraform-azurerm_update_management?ref=20200527.1"
name = "test"
resource_group_name = "my_resource_group_name"
azurerm_automation_account = azurerm_automation_account.my_azurerm_automation_account
operatingSystem = "Linux"
scope = [azurerm_resource_group.somerg1.id, azurerm_resource_group.somerg1.id]
timeZone = "EST"
startTime = "${local.update_date}T${local.update_time}:00+02:00"
weekDays = ["Sunday"]
}

AWS Codepipeline with Terraform - How to dynamically create stages

I designed an AWS codepipeline module using terraform module, I have multiple actual codepipelines using the codepipeline module. I use module as design pattern because all the codepipelines look similar, except that some of the codepipelines need approval stages, some do not need. How do I design the codepipeline module approval stages so that the actual codepipelines can be created based on different needs?
I tried to use count = 0 or 1 to control the stage but it does not work because the stage is not resource-level. Is there any tricky way or workaround?
I feel this link asked the similar question but I cannot figure out what is the answer:
Terraform & AWS CodePipeline - Dynamically define actions on a stage
Here is my codepipeline terraform module:
resource "aws_codepipeline" "dev" {
name = "my_codepipeline"
role_arn = ...
...
stage {
name = "Source"
...
}
stage {
name = "test"
...
}
stage {
# count = 0 # or 1. it does not work
name = "Approval"
action {
name = "Approval"
owner = "AWS"
category = "Approval"
provider = "Manual"
version = "1"
configuration {
NotificationArn = "..."
CustomData = "..."
ExternalEntityLink = "..."
}
}
}
stage {
name = "prod"
...
}
}
To dynamically add a stage (and not just an action) you could do the following:
dynamic "stage" {
for_each = var.production_approval ? [1] : []
content {
name = "Approve"
action {
configuration = {
NotificationArn = var.approve_sns_arn
CustomData = var.approve_comment
}
name = "Production-Approval"
category = "Approval"
owner = "AWS"
provider = "Manual"
version = "1"
}
}
}
When going through your use case, I have the feeling that it is very suitable with new terraform feature in v0.12.x
Below is a sample on how to use for_each to set dynamic target regions, you should be fine to do the same for stages.
dynamic "target_region" {
for_each = var.target_image_regions
content {
name = target_region.value
regional_replica_count = 1
}
}
let me know if this works for you or not.
Reference: https://www.hashicorp.com/blog/announcing-terraform-0-12
I figured that you can get this working in Terraform 0.12+ as BMW said, but only if you have number of block greater than 0.
At least 1 "action" blocks are required.
Unfortunately, my (and your) use case required 0/1 actions depending on the environment so we have to manage it manually for a while.
Cheers.
dynamic "action" {
for_each = local.production_approval # e.g. [] || [true]
content {
category = "Approval"
configuration = {}
input_artifacts = []
name = "Production-Approval"
output_artifacts = []
owner = "AWS"
provider = "Manual"
run_order = 1
version = "1"
}
}

Resources