diff --git a/azurerm/helpers/azure/automation.go b/azurerm/helpers/azure/automation.go new file mode 100644 index 000000000000..8b67d481c0d2 --- /dev/null +++ b/azurerm/helpers/azure/automation.go @@ -0,0 +1,32 @@ +package azure + +import ( + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +// ValidateAutomationAccountName validates Automation Account names +func ValidateAutomationAccountName() schema.SchemaValidateFunc { + return validation.StringMatch( + regexp.MustCompile(`^[0-9a-zA-Z][-0-9a-zA-Z]{4,48}[0-9a-zA-Z]$`), + `The account name must start with a letter or number. The account name can contain letters, numbers, and dashes. The final character must be a letter or a number. The account name length must be from 6 to 50 characters.`, + ) +} + +// ValidateAutomationRunbookName validates Automation Account Runbook names +func ValidateAutomationRunbookName() schema.SchemaValidateFunc { + return validation.StringMatch( + regexp.MustCompile(`^[0-9a-zA-Z][-_0-9a-zA-Z]{0,62}$`), + `The name can contain only letters, numbers, underscores and dashes. The name must begin with a letter. The name must be less than 64 characters.`, + ) +} + +// ValidateAutomationScheduleName validates Automation Account Schedule names +func ValidateAutomationScheduleName() schema.SchemaValidateFunc { + return validation.StringMatch( + regexp.MustCompile(`^[^<>*%&:\\?.+\/]{0,127}[^<>*%&:\\?.+\/\s]$`), + `The name length must be from 1 to 128 characters. The name cannot contain special characters < > * % & : \ ? . + / and cannot end with a whitespace character.`, + ) +} diff --git a/azurerm/internal/services/automation/client.go b/azurerm/internal/services/automation/client.go index b8038fcb76f2..bf534361e3da 100644 --- a/azurerm/internal/services/automation/client.go +++ b/azurerm/internal/services/automation/client.go @@ -11,6 +11,7 @@ type Client struct { CredentialClient *automation.CredentialClient DscConfigurationClient *automation.DscConfigurationClient DscNodeConfigurationClient *automation.DscNodeConfigurationClient + JobScheduleClient *automation.JobScheduleClient ModuleClient *automation.ModuleClient RunbookClient *automation.RunbookClient RunbookDraftClient *automation.RunbookDraftClient @@ -34,6 +35,9 @@ func BuildClient(o *common.ClientOptions) *Client { DscNodeConfigurationClient := automation.NewDscNodeConfigurationClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&DscNodeConfigurationClient.Client, o.ResourceManagerAuthorizer) + JobScheduleClient := automation.NewJobScheduleClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&JobScheduleClient.Client, o.ResourceManagerAuthorizer) + ModuleClient := automation.NewModuleClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&ModuleClient.Client, o.ResourceManagerAuthorizer) @@ -55,6 +59,7 @@ func BuildClient(o *common.ClientOptions) *Client { CredentialClient: &CredentialClient, DscConfigurationClient: &DscConfigurationClient, DscNodeConfigurationClient: &DscNodeConfigurationClient, + JobScheduleClient: &JobScheduleClient, ModuleClient: &ModuleClient, RunbookClient: &RunbookClient, RunbookDraftClient: &RunbookDraftClient, diff --git a/azurerm/provider.go b/azurerm/provider.go index 7f01f58317d3..af8da56a961f 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -187,6 +187,7 @@ func Provider() terraform.ResourceProvider { "azurerm_automation_credential": resourceArmAutomationCredential(), "azurerm_automation_dsc_configuration": resourceArmAutomationDscConfiguration(), "azurerm_automation_dsc_nodeconfiguration": resourceArmAutomationDscNodeConfiguration(), + "azurerm_automation_job_schedule": resourceArmAutomationJobSchedule(), "azurerm_automation_module": resourceArmAutomationModule(), "azurerm_automation_runbook": resourceArmAutomationRunbook(), "azurerm_automation_schedule": resourceArmAutomationSchedule(), diff --git a/azurerm/resource_arm_automation_job_schedule.go b/azurerm/resource_arm_automation_job_schedule.go new file mode 100644 index 000000000000..61f0799cd430 --- /dev/null +++ b/azurerm/resource_arm_automation_job_schedule.go @@ -0,0 +1,236 @@ +package azurerm + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/automation/mgmt/2015-10-31/automation" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + uuid "github.com/satori/go.uuid" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmAutomationJobSchedule() *schema.Resource { + return &schema.Resource{ + Create: resourceArmAutomationJobScheduleCreate, + Read: resourceArmAutomationJobScheduleRead, + Delete: resourceArmAutomationJobScheduleDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + + "resource_group_name": azure.SchemaResourceGroupName(), + + "automation_account_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateAutomationAccountName(), + }, + + "runbook_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateAutomationRunbookName(), + }, + + "schedule_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateAutomationScheduleName(), + }, + + "parameters": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + ValidateFunc: func(v interface{}, _ string) (warnings []string, errors []error) { + m := v.(map[string]interface{}) + + for k := range m { + if k != strings.ToLower(k) { + errors = append(errors, fmt.Errorf("Due to a bug in the implementation of Runbooks in Azure, the parameter names need to be specified in lowercase only. See: \"https://github.com/Azure/azure-sdk-for-go/issues/4780\" for more information.")) + } + } + + return warnings, errors + }, + }, + + "run_on": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "job_schedule_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceArmAutomationJobScheduleCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).Automation.JobScheduleClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() + + log.Printf("[INFO] preparing arguments for AzureRM Automation Job Schedule creation.") + + jobScheduleUUID := uuid.NewV4() + resGroup := d.Get("resource_group_name").(string) + accountName := d.Get("automation_account_name").(string) + + runbookName := d.Get("runbook_name").(string) + scheduleName := d.Get("schedule_name").(string) + + if requireResourcesToBeImported && d.IsNewResource() { + existing, err := client.Get(ctx, resGroup, accountName, jobScheduleUUID) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing Automation Job Schedule %q (Account %q / Resource Group %q): %s", jobScheduleUUID, accountName, resGroup, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_automation_job_schedule", *existing.ID) + } + } + + parameters := automation.JobScheduleCreateParameters{ + JobScheduleCreateProperties: &automation.JobScheduleCreateProperties{ + Schedule: &automation.ScheduleAssociationProperty{ + Name: &scheduleName, + }, + Runbook: &automation.RunbookAssociationProperty{ + Name: &runbookName, + }, + }, + } + properties := parameters.JobScheduleCreateProperties + + // parameters to be passed into the runbook + if v, ok := d.GetOk("parameters"); ok { + jsParameters := make(map[string]*string) + for k, v := range v.(map[string]interface{}) { + value := v.(string) + jsParameters[k] = &value + } + properties.Parameters = jsParameters + } + + if v, ok := d.GetOk("run_on"); ok { + value := v.(string) + properties.RunOn = &value + } + + if _, err := client.Create(ctx, resGroup, accountName, jobScheduleUUID, parameters); err != nil { + return err + } + + read, err := client.Get(ctx, resGroup, accountName, jobScheduleUUID) + if err != nil { + return err + } + + if read.ID == nil { + return fmt.Errorf("Cannot read Automation Job Schedule '%s' (Account %q / Resource Group %s) ID", jobScheduleUUID, accountName, resGroup) + } + + d.SetId(*read.ID) + + return resourceArmAutomationJobScheduleRead(d, meta) +} + +func resourceArmAutomationJobScheduleRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).Automation.JobScheduleClient + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + jobScheduleID := id.Path["jobSchedules"] + jobScheduleUUID := uuid.FromStringOrNil(jobScheduleID) + resGroup := id.ResourceGroup + accountName := id.Path["automationAccounts"] + + resp, err := client.Get(ctx, resGroup, accountName, jobScheduleUUID) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on AzureRM Automation Job Schedule '%s': %+v", jobScheduleUUID, err) + } + + d.Set("job_schedule_id", resp.JobScheduleID) + d.Set("resource_group_name", resGroup) + d.Set("automation_account_name", accountName) + d.Set("runbook_name", resp.JobScheduleProperties.Runbook.Name) + d.Set("schedule_name", resp.JobScheduleProperties.Schedule.Name) + + if v := resp.JobScheduleProperties.RunOn; v != nil { + d.Set("run_on", v) + } + + if v := resp.JobScheduleProperties.Parameters; v != nil { + jsParameters := make(map[string]interface{}) + for key, value := range v { + jsParameters[strings.ToLower(key)] = value + } + d.Set("parameters", jsParameters) + } + + return nil +} + +func resourceArmAutomationJobScheduleDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).Automation.JobScheduleClient + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + jobScheduleID := id.Path["jobSchedules"] + jobScheduleUUID := uuid.FromStringOrNil(jobScheduleID) + resGroup := id.ResourceGroup + accountName := id.Path["automationAccounts"] + + resp, err := client.Delete(ctx, resGroup, accountName, jobScheduleUUID) + if err != nil { + if !utils.ResponseWasNotFound(resp) { + return fmt.Errorf("Error issuing AzureRM delete request for Automation Job Schedule '%s': %+v", jobScheduleUUID, err) + } + } + + return nil +} diff --git a/azurerm/resource_arm_automation_job_schedule_test.go b/azurerm/resource_arm_automation_job_schedule_test.go new file mode 100644 index 000000000000..cc516712f74f --- /dev/null +++ b/azurerm/resource_arm_automation_job_schedule_test.go @@ -0,0 +1,323 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + uuid "github.com/satori/go.uuid" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMAutomationJobSchedule_basic(t *testing.T) { + resourceName := "azurerm_automation_job_schedule.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAutomationJobScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAutomationJobSchedule_basic(ri, testLocation()), + Check: checkAccAzureRMAutomationJobSchedule_basic(resourceName), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMAutomationJobSchedule_complete(t *testing.T) { + resourceName := "azurerm_automation_job_schedule.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAutomationJobScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAutomationJobSchedule_complete(ri, testLocation()), + Check: checkAccAzureRMAutomationJobSchedule_complete(resourceName), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMAutomationJobSchedule_update(t *testing.T) { + resourceName := "azurerm_automation_job_schedule.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAutomationJobScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAutomationJobSchedule_basic(ri, testLocation()), + Check: checkAccAzureRMAutomationJobSchedule_basic(resourceName), + }, + { + Config: testAccAzureRMAutomationJobSchedule_complete(ri, testLocation()), + Check: checkAccAzureRMAutomationJobSchedule_complete(resourceName), + }, + { + Config: testAccAzureRMAutomationJobSchedule_basic(ri, testLocation()), + Check: checkAccAzureRMAutomationJobSchedule_basic(resourceName), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMAutomationJobSchedule_requiresImport(t *testing.T) { + if !requireResourcesToBeImported { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_automation_job_schedule.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAutomationJobScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAutomationJobSchedule_basic(ri, location), + Check: checkAccAzureRMAutomationJobSchedule_basic(resourceName), + }, + { + Config: testAccAzureRMAutomationJobSchedule_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_automation_job_schedule"), + }, + }, + }) +} + +func testCheckAzureRMAutomationJobScheduleDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).Automation.JobScheduleClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_automation_job_schedule" { + continue + } + + id, err := parseAzureResourceID(rs.Primary.Attributes["id"]) + if err != nil { + return err + } + jobScheduleID := id.Path["jobSchedules"] + jobScheduleUUID := uuid.FromStringOrNil(jobScheduleID) + accName := rs.Primary.Attributes["account_name"] + + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Automation Job Schedule: '%s'", jobScheduleUUID) + } + + resp, err := conn.Get(ctx, resourceGroup, accName, jobScheduleUUID) + + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + + return err + } + + return fmt.Errorf("Automation Job Schedule still exists:\n%#v", resp) + } + + return nil +} + +func testCheckAzureRMAutomationJobScheduleExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).Automation.JobScheduleClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + id, err := parseAzureResourceID(rs.Primary.Attributes["id"]) + if err != nil { + return err + } + jobScheduleID := id.Path["jobSchedules"] + jobScheduleUUID := uuid.FromStringOrNil(jobScheduleID) + accName := rs.Primary.Attributes["automation_account_name"] + + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Automation Job Schedule: '%s'", jobScheduleUUID) + } + + resp, err := conn.Get(ctx, resourceGroup, accName, jobScheduleUUID) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Automation Job Schedule '%s' (Account %q / Resource Group %q) does not exist", jobScheduleUUID, accName, resourceGroup) + } + + return fmt.Errorf("Bad: Get on automationJobScheduleClient: %+v", err) + } + + return nil + } +} + +func testAccAzureRMAutomationJobSchedulePrerequisites(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_automation_account" "test" { + name = "acctestAA-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + name = "Basic" + } +} + +resource "azurerm_automation_runbook" "test" { + name = "Output-HelloWorld" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + account_name = "${azurerm_automation_account.test.name}" + log_verbose = "true" + log_progress = "true" + description = "This is a test runbook for terraform acceptance test" + runbook_type = "PowerShell" + + publish_content_link { + uri = "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/c4935ffb69246a6058eb24f54640f53f69d3ac9f/101-automation-runbook-getvms/Runbooks/Get-AzureVMTutorial.ps1" + } + + content = < azurerm_storage_account - +
  • azurerm_storage_account_blob_container_sas
  • @@ -444,8 +444,8 @@
  • azurerm_api_management_api_schema
  • - -
  • + +
  • azurerm_api_management_subscription
  • @@ -602,6 +602,10 @@ azurerm_automation_dsc_nodeconfiguration +
  • + azurerm_automation_job_schedule +
  • +
  • azurerm_automation_module
  • @@ -901,7 +905,7 @@
  • azurerm_sql_failover_group
  • - +
  • azurerm_sql_firewall_rule
  • @@ -1732,7 +1736,7 @@
  • azurerm_stream_analytics_output_eventhub -
  • +
  • azurerm_stream_analytics_output_servicebus_queue @@ -1767,7 +1771,7 @@
  • azurerm_storage_blob
  • - +
  • azurerm_storage_container
  • diff --git a/website/docs/r/automation_job_schedule.html.markdown b/website/docs/r/automation_job_schedule.html.markdown new file mode 100644 index 000000000000..14eb1b181300 --- /dev/null +++ b/website/docs/r/automation_job_schedule.html.markdown @@ -0,0 +1,61 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_automation_job_schedule" +sidebar_current: "docs-azurerm-resource-automation-job-schedule" +description: |- + Links an Automation Runbook and Schedule. +--- + +# azurerm_automation_job_schedule + +Links an Automation Runbook and Schedule. + +## Example Usage + +This is an example of just the Job Schedule. A full example of the `azurerm_automation_job_schedule` resource can be found in [the `./examples/automation-account` directory within the Github Repository](https://github.com/terraform-providers/terraform-provider-azurerm/tree/master/examples/automation-account) + +```hcl +resource "azurerm_automation_job_schedule" "example" { + resource_group_name = "tf-rgr-automation" + automation_account_name = "tf-automation-account" + schedule_name = "hour" + runbook_name = "Get-VirtualMachine" + + parameters = { + resourcegroup = "tf-rgr-vm" + vmname = "TF-VM-01" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `resource_group_name` - (Required) The name of the resource group in which the Job Schedule is created. Changing this forces a new resource to be created. + +* `automation_account_name` - (Required) The name of the Automation Account in which the Job Schedule is created. Changing this forces a new resource to be created. + +* `runbook_name` - (Required) The name of a Runbook to link to a Schedule. It needs to be in the same Automation Account as the Schedule and Job Schedule. Changing this forces a new resource to be created. + +* `parameters` - (Optional) A map of key/value pairs corresponding to the arguments that can be passed to the Runbook. Changing this forces a new resource to be created. + +-> **NOTE:** The parameter keys/names must strictly be in lowercase, even if this is not the case in the runbook. This is due to a limitation in Azure Automation where the parameter names are normalized. The values specified don't have this limitation. + +* `run_on` - (Optional) Name of a Hybrid Worker Group the Runbook will be executed on. Changing this forces a new resource to be created. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Automation Job Schedule's full ID. + +* `job_schedule_id` - The UUID identifying the Automation Job Schedule. + +## Import + +Automation Job Schedule can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_automation_job_schedule.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Automation/automationAccounts/account1/jobSchedules/10000000-1001-1001-1001-000000000001 +``` diff --git a/website/docs/r/automation_runbook.html.markdown b/website/docs/r/automation_runbook.html.markdown index 6c3c88f2e3a1..9391b58a521a 100644 --- a/website/docs/r/automation_runbook.html.markdown +++ b/website/docs/r/automation_runbook.html.markdown @@ -39,7 +39,7 @@ resource "azurerm_automation_runbook" "example" { runbook_type = "PowerShellWorkflow" publish_content_link { - uri = "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-automation-runbook-getvms/Runbooks/Get-AzureVMTutorial.ps1" + uri = "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/c4935ffb69246a6058eb24f54640f53f69d3ac9f/101-automation-runbook-getvms/Runbooks/Get-AzureVMTutorial.ps1" } } ``` @@ -77,7 +77,7 @@ resource "azurerm_automation_runbook" "example" { runbook_type = "PowerShell" publish_content_link { - uri = "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-automation-runbook-getvms/Runbooks/Get-AzureVMTutorial.ps1" + uri = "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/c4935ffb69246a6058eb24f54640f53f69d3ac9f/101-automation-runbook-getvms/Runbooks/Get-AzureVMTutorial.ps1" } content = "${data.local_file.example.content}"