2
votes

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"
}
2

2 Answers

1
votes

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.

1
votes

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"]
}