diff --git a/azurerm/config.go b/azurerm/config.go index 82fa2a54fb62..52ab486cb646 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -34,6 +34,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-06-01/subscriptions" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-09-01/locks" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2017-05-10/resources" + "github.com/Azure/azure-sdk-for-go/services/scheduler/mgmt/2016-03-01/scheduler" "github.com/Azure/azure-sdk-for-go/services/search/mgmt/2015-08-19/search" "github.com/Azure/azure-sdk-for-go/services/servicebus/mgmt/2017-04-01/servicebus" "github.com/Azure/azure-sdk-for-go/services/sql/mgmt/2015-05-01-preview/sql" @@ -170,6 +171,9 @@ type ArmClient struct { serviceBusTopicsClient servicebus.TopicsClient serviceBusSubscriptionsClient servicebus.SubscriptionsClient + //Scheduler + schedulerJobCollectionsClient scheduler.JobCollectionsClient + // Storage storageServiceClient storage.AccountsClient storageUsageClient storage.UsageClient @@ -355,6 +359,7 @@ func getArmClient(c *authentication.Config) (*ArmClient, error) { client.registerResourcesClients(endpoint, c.SubscriptionID, auth) client.registerSearchClients(endpoint, c.SubscriptionID, auth) client.registerServiceBusClients(endpoint, c.SubscriptionID, auth) + client.registerSchedulerClients(endpoint, c.SubscriptionID, auth) client.registerStorageClients(endpoint, c.SubscriptionID, auth) client.registerTrafficManagerClients(endpoint, c.SubscriptionID, auth) client.registerWebClients(endpoint, c.SubscriptionID, auth) @@ -802,6 +807,12 @@ func (c *ArmClient) registerServiceBusClients(endpoint, subscriptionId string, a c.serviceBusSubscriptionsClient = subscriptionsClient } +func (c *ArmClient) registerSchedulerClients(endpoint, subscriptionId string, auth autorest.Authorizer) { + jobsClient := scheduler.NewJobCollectionsClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&jobsClient.Client, auth) + c.schedulerJobCollectionsClient = jobsClient +} + func (c *ArmClient) registerStorageClients(endpoint, subscriptionId string, auth autorest.Authorizer) { accountsClient := storage.NewAccountsClientWithBaseURI(endpoint, subscriptionId) c.configureClient(&accountsClient.Client, auth) diff --git a/azurerm/provider.go b/azurerm/provider.go index b7ac20ac2fee..fab2519de6fa 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -181,6 +181,7 @@ func Provider() terraform.ResourceProvider { "azurerm_servicebus_topic": resourceArmServiceBusTopic(), "azurerm_servicebus_topic_authorization_rule": resourceArmServiceBusTopicAuthorizationRule(), "azurerm_snapshot": resourceArmSnapshot(), + "azurerm_scheduler_job_collection": resourceArmSchedulerJobCollection(), "azurerm_sql_database": resourceArmSqlDatabase(), "azurerm_sql_elasticpool": resourceArmSqlElasticPool(), "azurerm_sql_firewall_rule": resourceArmSqlFirewallRule(), diff --git a/azurerm/resource_arm_scheduler_job_collection.go b/azurerm/resource_arm_scheduler_job_collection.go new file mode 100644 index 000000000000..f668e496c8ee --- /dev/null +++ b/azurerm/resource_arm_scheduler_job_collection.go @@ -0,0 +1,274 @@ +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/scheduler/mgmt/2016-03-01/scheduler" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSchedulerJobCollection() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSchedulerJobCollectionCreateUpdate, + Read: resourceArmSchedulerJobCollectionRead, + Update: resourceArmSchedulerJobCollectionCreateUpdate, + Delete: resourceArmSchedulerJobCollectionDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": locationSchema(), + + "resource_group_name": resourceGroupNameSchema(), + + "tags": tagsSchema(), + + "sku": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + ValidateFunc: validation.StringInSlice([]string{ + string(scheduler.Free), + string(scheduler.Standard), + string(scheduler.P10Premium), + string(scheduler.P20Premium), + }, true), + }, + + //optional + "state": { + Type: schema.TypeString, + Optional: true, + Default: string(scheduler.Enabled), + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + ValidateFunc: validation.StringInSlice([]string{ + string(scheduler.Enabled), + string(scheduler.Suspended), + string(scheduler.Disabled), + }, true), + }, + + "quota": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + + //max_job_occurrence doesn't seem to do anything and always remains empty + + "max_job_count": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(0), + }, + + "max_recurrence_frequency": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + ValidateFunc: validation.StringInSlice([]string{ + string(scheduler.Minute), + string(scheduler.Hour), + string(scheduler.Day), + string(scheduler.Week), + string(scheduler.Month), + }, true), + }, + + //this sets MaxRecurrance.Interval, and the documentation in the api states: + // Gets or sets the interval between retries. + "max_retry_interval": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), //changes depending on the frequency, unknown maximums + }, + }, + }, + }, + }, + } +} + +func resourceArmSchedulerJobCollectionCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).schedulerJobCollectionsClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + location := d.Get("location").(string) + resourceGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + log.Printf("[DEBUG] Creating/updating Scheduler Job Collection %q (resource group %q)", name, resourceGroup) + + collection := scheduler.JobCollectionDefinition{ + Location: utils.String(location), + Tags: expandTags(tags), + Properties: &scheduler.JobCollectionProperties{ + Sku: &scheduler.Sku{ + Name: scheduler.SkuDefinition(d.Get("sku").(string)), + }, + }, + } + + if state, ok := d.Get("state").(string); ok { + collection.Properties.State = scheduler.JobCollectionState(state) + } + collection.Properties.Quota = expandAzureArmSchedulerJobCollectionQuota(d) + + //create job collection + collection, err := client.CreateOrUpdate(ctx, resourceGroup, name, collection) + if err != nil { + return fmt.Errorf("Error creating/updating Scheduler Job Collection %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + //ensure collection actually exists and we have the correct ID + collection, err = client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error reading Scheduler Job Collection %q after create/update (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.SetId(*collection.ID) + + return resourceArmSchedulerJobCollectionPopulate(d, resourceGroup, &collection) +} + +func resourceArmSchedulerJobCollectionRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).schedulerJobCollectionsClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + name := id.Path["jobCollections"] + resourceGroup := id.ResourceGroup + + log.Printf("[DEBUG] Reading Scheduler Job Collection %q (resource group %q)", name, resourceGroup) + + collection, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(collection.Response) { + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on Scheduler Job Collection %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + return resourceArmSchedulerJobCollectionPopulate(d, resourceGroup, &collection) +} + +func resourceArmSchedulerJobCollectionPopulate(d *schema.ResourceData, resourceGroup string, collection *scheduler.JobCollectionDefinition) error { + + //standard properties + d.Set("name", collection.Name) + d.Set("location", azureRMNormalizeLocation(*collection.Location)) + d.Set("resource_group_name", resourceGroup) + flattenAndSetTags(d, collection.Tags) + + //resource specific + if properties := collection.Properties; properties != nil { + if sku := properties.Sku; sku != nil { + d.Set("sku", sku.Name) + } + d.Set("state", string(properties.State)) + + if err := d.Set("quota", flattenAzureArmSchedulerJobCollectionQuota(properties.Quota)); err != nil { + return fmt.Errorf("Error flattening quota for Job Collection %q (Resource Group %q): %+v", collection.Name, resourceGroup, err) + } + } + + return nil +} + +func resourceArmSchedulerJobCollectionDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).schedulerJobCollectionsClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + name := id.Path["jobCollections"] + resourceGroup := id.ResourceGroup + + log.Printf("[DEBUG] Deleting Scheduler Job Collection %q (resource group %q)", name, resourceGroup) + + future, err := client.Delete(ctx, resourceGroup, name) + if err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error issuing delete request for Scheduler Job Collection %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + err = future.WaitForCompletion(ctx, client.Client) + if err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error waiting for deletion of Scheduler Job Collection %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + return nil +} + +func expandAzureArmSchedulerJobCollectionQuota(d *schema.ResourceData) *scheduler.JobCollectionQuota { + if qb, ok := d.Get("quota").([]interface{}); ok && len(qb) > 0 { + quota := scheduler.JobCollectionQuota{ + MaxRecurrence: &scheduler.JobMaxRecurrence{}, + } + + quotaBlock := qb[0].(map[string]interface{}) + + if v, ok := quotaBlock["max_job_count"].(int); ok { + quota.MaxJobCount = utils.Int32(int32(v)) + } + if v, ok := quotaBlock["max_recurrence_frequency"].(string); ok { + quota.MaxRecurrence.Frequency = scheduler.RecurrenceFrequency(v) + } + if v, ok := quotaBlock["max_retry_interval"].(int); ok { + quota.MaxRecurrence.Interval = utils.Int32(int32(v)) + } + + return "a + } + + return nil +} + +func flattenAzureArmSchedulerJobCollectionQuota(quota *scheduler.JobCollectionQuota) []interface{} { + + if quota == nil { + return nil + } + + quotaBlock := make(map[string]interface{}) + + if v := quota.MaxJobCount; v != nil { + quotaBlock["max_job_count"] = *v + } + if recurrence := quota.MaxRecurrence; recurrence != nil { + if v := recurrence.Interval; v != nil { + quotaBlock["max_retry_interval"] = *v + } + + quotaBlock["max_recurrence_frequency"] = string(recurrence.Frequency) + } + + return []interface{}{quotaBlock} +} diff --git a/azurerm/resource_arm_scheduler_job_collection_test.go b/azurerm/resource_arm_scheduler_job_collection_test.go new file mode 100644 index 000000000000..a18e5e05fcdf --- /dev/null +++ b/azurerm/resource_arm_scheduler_job_collection_test.go @@ -0,0 +1,154 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/scheduler/mgmt/2016-03-01/scheduler" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMSchedulerJobCollection_basic(t *testing.T) { + ri := acctest.RandInt() + resourceName := "azurerm_scheduler_job_collection.test" + config := testAccAzureRMSchedulerJobCollection_basic(ri, testLocation(), "") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobCollectionDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSchedulerJobCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "state", string(scheduler.Enabled)), + ), + }, + }, + }) +} + +func TestAccAzureRMSchedulerJobCollection_complete(t *testing.T) { + ri := acctest.RandInt() + resourceName := "azurerm_scheduler_job_collection.test" + preConfig := testAccAzureRMSchedulerJobCollection_basic(ri, testLocation(), "") + config := testAccAzureRMSchedulerJobCollection_complete(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobCollectionDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSchedulerJobCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "state", string(scheduler.Enabled)), + ), + }, + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSchedulerJobCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "state", string(scheduler.Disabled)), + resource.TestCheckResourceAttr(resourceName, "quota.0.max_job_count", "10"), + resource.TestCheckResourceAttr(resourceName, "quota.0.max_retry_interval", "10"), + resource.TestCheckResourceAttr(resourceName, "quota.0.max_recurrence_frequency", "hour"), + ), + }, + }, + }) +} + +func testCheckAzureRMSchedulerJobCollectionDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_scheduler_job_collection" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + client := testAccProvider.Meta().(*ArmClient).schedulerJobCollectionsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, name) + + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + + return err + } + + return fmt.Errorf("Scheduler Job Collection still exists:\n%#v", resp) + } + + return nil +} + +func testCheckAzureRMSchedulerJobCollectionExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %q", name) + } + + name := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Scheduler Job Collection: %q", name) + } + + client := testAccProvider.Meta().(*ArmClient).schedulerJobCollectionsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + resp, err := client.Get(ctx, resourceGroup, name) + + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Scheduler Job Collection %q (resource group: %q) was not found: %+v", name, resourceGroup, err) + } + + return fmt.Errorf("Bad: Get on schedulerJobCollectionsClient: %+v", err) + } + + return nil + } +} + +func testAccAzureRMSchedulerJobCollection_basic(rInt int, location string, additional string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_scheduler_job_collection" "test" { + name = "acctest-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +%s +} +`, rInt, location, rInt, additional) +} + +func testAccAzureRMSchedulerJobCollection_complete(rInt int, location string) string { + return testAccAzureRMSchedulerJobCollection_basic(rInt, location, ` + state = "disabled" + quota { + max_recurrence_frequency = "Hour" + max_retry_interval = 10 + max_job_count = 10 + } +`) +} diff --git a/examples/scheduler-jobs/main.tf b/examples/scheduler-jobs/main.tf new file mode 100644 index 000000000000..483cacc85129 --- /dev/null +++ b/examples/scheduler-jobs/main.tf @@ -0,0 +1,19 @@ + +resource "azurerm_resource_group" "rg" { + name = "${var.resource_group_name}" + location = "${var.resource_group_location}" +} + +resource "azurerm_scheduler_job_collection" "jobs" { + name = "example_job_collection" + location = "${azurerm_resource_group.rg.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + sku = "free" + state = "enabled" + + quota { + max_job_count = 5 + max_retry_interval = 24 + max_recurrence_frequency = "hour" + } +} diff --git a/examples/scheduler-jobs/outputs.tf b/examples/scheduler-jobs/outputs.tf new file mode 100644 index 000000000000..feb8f4d27f83 --- /dev/null +++ b/examples/scheduler-jobs/outputs.tf @@ -0,0 +1,4 @@ + +output "job_collection-id" { + value = "${azurerm_scheduler_job_collection.jobs.id}" +} \ No newline at end of file diff --git a/examples/scheduler-jobs/variables.tf b/examples/scheduler-jobs/variables.tf new file mode 100644 index 000000000000..e2c3abb36620 --- /dev/null +++ b/examples/scheduler-jobs/variables.tf @@ -0,0 +1,11 @@ +variable "resource_group_name" { + type = "string" + description = "Name of the azure resource group." + default = "tfex-job_collection" +} + +variable "resource_group_location" { + type = "string" + description = "Location of the azure resource group." + default = "westus" +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 9912e43d8711..35a21265e364 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -612,6 +612,15 @@ +