diff --git a/azurerm/config.go b/azurerm/config.go index 5150d7f6bacb..767d92303191 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -190,6 +190,10 @@ type ArmClient struct { resourceGroupsClient resources.GroupsClient subscriptionsClient subscriptions.Client + //Scheduler + schedulerJobCollectionsClient scheduler.JobCollectionsClient + schedulerJobsClient scheduler.JobsClient + // Search searchServicesClient search.ServicesClient @@ -200,9 +204,6 @@ type ArmClient struct { serviceBusSubscriptionsClient servicebus.SubscriptionsClient serviceBusSubscriptionRulesClient servicebus.RulesClient - //Scheduler - schedulerJobCollectionsClient scheduler.JobCollectionsClient - // Storage storageServiceClient storage.AccountsClient storageUsageClient storage.UsageClient @@ -868,6 +869,16 @@ func (c *ArmClient) registerResourcesClients(endpoint, subscriptionId string, au c.subscriptionsClient = subscriptionsClient } +func (c *ArmClient) registerSchedulerClients(endpoint, subscriptionId string, auth autorest.Authorizer) { + jobCollectionsClient := scheduler.NewJobCollectionsClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&jobCollectionsClient.Client, auth) + c.schedulerJobCollectionsClient = jobCollectionsClient + + jobsClient := scheduler.NewJobsClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&jobsClient.Client, auth) + c.schedulerJobsClient = jobsClient +} + func (c *ArmClient) registerSearchClients(endpoint, subscriptionId string, auth autorest.Authorizer) { searchClient := search.NewServicesClientWithBaseURI(endpoint, subscriptionId) c.configureClient(&searchClient.Client, auth) @@ -896,12 +907,6 @@ func (c *ArmClient) registerServiceBusClients(endpoint, subscriptionId string, a c.serviceBusSubscriptionRulesClient = subscriptionRulesClient } -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/helpers/set/set.go b/azurerm/helpers/set/set.go new file mode 100644 index 000000000000..0968ad23de28 --- /dev/null +++ b/azurerm/helpers/set/set.go @@ -0,0 +1,35 @@ +package set + +import ( + "strconv" + "strings" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func HashInt(v interface{}) int { + return hashcode.String(strconv.Itoa(v.(int))) +} + +func HashStringIgnoreCase(v interface{}) int { + return hashcode.String(strings.ToLower(v.(string))) +} + +func FromInt32Slice(slice []int32) *schema.Set { + set := &schema.Set{F: HashInt} + for _, v := range slice { + set.Add(int(v)) + } + + return set +} + +func ToSliceInt32P(set *schema.Set) *[]int32 { + var slice []int32 + for _, m := range set.List() { + slice = append(slice, int32(m.(int))) + } + + return &slice +} diff --git a/azurerm/helpers/validate/validate.go b/azurerm/helpers/validate/validate.go new file mode 100644 index 000000000000..1fde2d3a9e01 --- /dev/null +++ b/azurerm/helpers/validate/validate.go @@ -0,0 +1,71 @@ +package validate + +import ( + "fmt" + "net/url" + + "strings" + + "github.com/hashicorp/terraform/helper/schema" +) + +func IntBetweenAndNot(min, max, not int) schema.SchemaValidateFunc { + return func(i interface{}, k string) (_ []string, errors []error) { + v, ok := i.(int) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be int", k)) + return + } + + if v < min || v > max { + errors = append(errors, fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v)) + return + } + + if v == not { + errors = append(errors, fmt.Errorf("expected %s to not be %d, got %d", k, not, v)) + return + } + + return + } +} + +func UrlIsHttpOrHttps() schema.SchemaValidateFunc { + return UrlWithScheme([]string{"http", "https"}) +} + +func UrlWithScheme(validSchemes []string) schema.SchemaValidateFunc { + return func(i interface{}, k string) (_ []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + url, err := url.Parse(v) + if err != nil { + errors = append(errors, fmt.Errorf("%q url is in an invalid format: %q (%+v)", k, i, err)) + return + } + + if url.Host == "" { + errors = append(errors, fmt.Errorf("%q url has no host: %q", k, url)) + } + + found := false + for _, s := range validSchemes { + if strings.EqualFold(url.Scheme, s) { + found = true + break + } + } + + if !found { + schemes := strings.Join(validSchemes, ",") + errors = append(errors, fmt.Errorf("URL scheme %q isn't valid - supported scheme's are %q", url.Scheme, schemes)) + } + + return + } +} diff --git a/azurerm/helpers/validate/validate_test.go b/azurerm/helpers/validate/validate_test.go new file mode 100644 index 000000000000..4011884c879f --- /dev/null +++ b/azurerm/helpers/validate/validate_test.go @@ -0,0 +1,45 @@ +package validate + +import "testing" + +func TestUrlWithScheme(t *testing.T) { + validSchemes := []string{"example"} + testCases := []struct { + Url string + ShouldHaveError bool + }{ + { + Url: "example://mysite.com", + ShouldHaveError: false, + }, + { + Url: "http://mysite.com", + ShouldHaveError: true, + }, + { + Url: "example://", + ShouldHaveError: true, + }, + { + Url: "example://validhost", + ShouldHaveError: false, + }, + } + + t.Run("TestUrlWithScheme", func(t *testing.T) { + for _, v := range testCases { + _, errors := UrlWithScheme(validSchemes)(v.Url, "field_name") + + hasErrors := len(errors) > 0 + if v.ShouldHaveError && !hasErrors { + t.Fatalf("Expected an error but didn't get one for %q", v.Url) + return + } + + if !v.ShouldHaveError && hasErrors { + t.Fatalf("Expected %q to return no errors, but got some %+v", v.Url, errors) + return + } + } + }) +} diff --git a/azurerm/provider.go b/azurerm/provider.go index a2a9bfe32577..c9ad8f102de9 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -207,6 +207,7 @@ func Provider() terraform.ResourceProvider { "azurerm_servicebus_topic": resourceArmServiceBusTopic(), "azurerm_servicebus_topic_authorization_rule": resourceArmServiceBusTopicAuthorizationRule(), "azurerm_snapshot": resourceArmSnapshot(), + "azurerm_scheduler_job": resourceArmSchedulerJob(), "azurerm_scheduler_job_collection": resourceArmSchedulerJobCollection(), "azurerm_sql_database": resourceArmSqlDatabase(), "azurerm_sql_elasticpool": resourceArmSqlElasticPool(), diff --git a/azurerm/resource_arm_scheduler_job.go b/azurerm/resource_arm_scheduler_job.go new file mode 100644 index 000000000000..4ecc68121598 --- /dev/null +++ b/azurerm/resource_arm_scheduler_job.go @@ -0,0 +1,911 @@ +package azurerm + +import ( + "bytes" + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/scheduler/mgmt/2016-03-01/scheduler" + "github.com/Azure/go-autorest/autorest/date" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/set" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSchedulerJob() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSchedulerJobCreateUpdate, + Read: resourceArmSchedulerJobRead, + Update: resourceArmSchedulerJobCreateUpdate, + Delete: resourceArmSchedulerJobDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + CustomizeDiff: resourceArmSchedulerJobCustomizeDiff, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile("^[a-zA-Z][-_a-zA-Z0-9].*$"), + "Job Collection Name name must start with a letter and contain only letters, numbers, hyphens and underscores.", + ), + }, + + "resource_group_name": resourceGroupNameSchema(), + + "job_collection_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + + //actions + "action_web": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: resourceArmSchedulerJobActionWebSchema("action_web"), + }, + + //actions + "error_action_web": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: resourceArmSchedulerJobActionWebSchema("error_action_web"), + }, + + //retry policy + "retry": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + + //silently fails if the duration is not in the correct format + //todo validation + "interval": { + Type: schema.TypeString, + Optional: true, + Default: "00:00:30", + ValidateFunc: validation.NoZeroValues, + }, + + "count": { + Type: schema.TypeInt, + Optional: true, + Default: 4, + ValidateFunc: validation.IntBetween(1, 20), + }, + }, + }, + }, + + //recurrences (schedule in portal, recurrence in API) + "recurrence": { + Type: schema.TypeList, + MinItems: 1, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + + "frequency": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validation.StringInSlice([]string{ + string(scheduler.Minute), + string(scheduler.Hour), + string(scheduler.Day), + string(scheduler.Week), + string(scheduler.Month), + }, true), + }, + + "interval": { + Type: schema.TypeInt, + Optional: true, + Default: 1, //defaults to 1 in the portal + + //maximum is dynamic: 1 min <= interval * frequency <= 500 days (bounded by JobCollection quotas) + ValidateFunc: validation.IntAtLeast(1), + }, + + "count": { + Type: schema.TypeInt, + Optional: true, + //silently fails/produces odd results at >2147483647 + ValidateFunc: validation.IntBetween(1, 2147483647), + }, + + "end_time": { + Type: schema.TypeString, + Optional: true, + Computed: true, + DiffSuppressFunc: suppress.RFC3339Time, + ValidateFunc: validate.RFC3339Time, + }, + + "minutes": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + ValidateFunc: validation.IntBetween(0, 59), + }, + Set: set.HashInt, + }, + + "hours": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeInt, + ValidateFunc: validation.IntBetween(0, 23), + }, + Set: set.HashInt, + }, + + "week_days": { //used with weekly + Type: schema.TypeSet, + Optional: true, + ConflictsWith: []string{"recurrence.0.month_days", "recurrence.0.monthly_occurrences"}, + // the constants are title cased but the API returns all lowercase + // so lets ignore the case + Set: set.HashStringIgnoreCase, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validation.StringInSlice([]string{ + string(scheduler.Sunday), + string(scheduler.Monday), + string(scheduler.Tuesday), + string(scheduler.Wednesday), + string(scheduler.Thursday), + string(scheduler.Friday), + string(scheduler.Saturday), + }, true), + }, + }, + + "month_days": { //used with monthly, + Type: schema.TypeSet, + Optional: true, + ConflictsWith: []string{"recurrence.0.week_days", "recurrence.0.monthly_occurrences"}, + MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeInt, + ValidateFunc: validate.IntBetweenAndNot(-31, 31, 0), + }, + Set: set.HashInt, + }, + + "monthly_occurrences": { + Type: schema.TypeSet, + Optional: true, + ConflictsWith: []string{"recurrence.0.week_days", "recurrence.0.month_days"}, + MinItems: 1, + Set: resourceAzureRMSchedulerJobMonthlyOccurrenceHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "day": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validation.StringInSlice([]string{ + string(scheduler.Sunday), + string(scheduler.Monday), + string(scheduler.Tuesday), + string(scheduler.Wednesday), + string(scheduler.Thursday), + string(scheduler.Friday), + string(scheduler.Saturday), + }, true), + }, + + "occurrence": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validate.IntBetweenAndNot(-5, 5, 0), + }, + }, + }, + }, + }, + }, + }, + + "start_time": { + Type: schema.TypeString, + Optional: true, + Computed: true, //defaults to now in create function + DiffSuppressFunc: suppress.RFC3339Time, + ValidateFunc: validate.RFC3339Time, //times in the past just start immediately + }, + + "state": { + Type: schema.TypeString, + Optional: true, + Computed: true, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validation.StringInSlice([]string{ + string(scheduler.JobStateEnabled), + string(scheduler.JobStateDisabled), + // JobStateFaulted & JobStateCompleted are also possible, but silly + }, true), + }, + }, + } +} + +func resourceArmSchedulerJobActionWebSchema(propertyName string) *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + // we can determine the type (HTTP/HTTPS) from the url + // but we need to make sure the url starts with http/https + // both so we can determine the type and as azure requires it + "url": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validate.UrlIsHttpOrHttps(), + }, + + "method": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "Get", "Put", "Post", "Delete", + }, true), + }, + + //only valid/used when action type is put + "body": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.NoZeroValues, + }, + + "headers": { + Type: schema.TypeMap, + Optional: true, + }, + + //authentication requires HTTPS + "authentication_basic": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: []string{ + fmt.Sprintf("%s.0.authentication_certificate", propertyName), + fmt.Sprintf("%s.0.authentication_active_directory", propertyName), + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "username": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + + "password": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + + "authentication_certificate": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ConflictsWith: []string{ + fmt.Sprintf("%s.0.authentication_basic", propertyName), + fmt.Sprintf("%s.0.authentication_active_directory", propertyName), + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pfx": { + Type: schema.TypeString, + Required: true, + Sensitive: true, //sensitive & shortens diff + ValidateFunc: validation.NoZeroValues, + }, + + "password": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validation.NoZeroValues, + }, + + "thumbprint": { + Type: schema.TypeString, + Computed: true, + }, + + "expiration": { + Type: schema.TypeString, + Computed: true, + }, + + "subject_name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "authentication_active_directory": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ConflictsWith: []string{ + fmt.Sprintf("%s.0.authentication_basic", propertyName), + fmt.Sprintf("%s.0.authentication_certificate", propertyName), + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "tenant_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + + "client_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validation.NoZeroValues, + }, + + "audience": { + Type: schema.TypeString, + Optional: true, + Computed: true, //is defaulted to the ServiceManagementEndpoint in create + }, + }, + }, + }, + }, + } +} + +func resourceArmSchedulerJobCustomizeDiff(diff *schema.ResourceDiff, v interface{}) error { + + _, hasWeb := diff.GetOk("action_web") + if !hasWeb { + return fmt.Errorf("One of `action_web`, `action_servicebus` or `action_storagequeue` must be set") + } + + if b, ok := diff.GetOk("recurrence"); ok { + if recurrence, ok := b.([]interface{})[0].(map[string]interface{}); ok { + + //if neither count nor end time is set the API will silently fail + _, hasCount := recurrence["count"] + _, hasEnd := recurrence["end_time"] + if !hasCount && !hasEnd { + return fmt.Errorf("One of `count` or `end_time` must be set for the 'recurrence' block.") + } + } + } + + return nil +} + +func resourceArmSchedulerJobCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).schedulerJobsClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + jobCollection := d.Get("job_collection_name").(string) + + job := scheduler.JobDefinition{ + Properties: &scheduler.JobProperties{ + Action: expandAzureArmSchedulerJobAction(d, meta), + }, + } + + log.Printf("[DEBUG] Creating/updating Scheduler Job %q (resource group %q)", name, resourceGroup) + + //schedule (recurrence) + if b, ok := d.GetOk("recurrence"); ok { + job.Properties.Recurrence = expandAzureArmSchedulerJobRecurrence(b) + } + + //start time, should be validated by schema, also defaults to now if not set + if v, ok := d.GetOk("start_time"); ok { + startTime, _ := time.Parse(time.RFC3339, v.(string)) + job.Properties.StartTime = &date.Time{Time: startTime} + } else { + job.Properties.StartTime = &date.Time{Time: time.Now()} + } + + //state + if state, ok := d.GetOk("state"); ok { + job.Properties.State = scheduler.JobState(state.(string)) + } + + resp, err := client.CreateOrUpdate(ctx, resourceGroup, jobCollection, name, job) + if err != nil { + return fmt.Errorf("Error creating/updating Scheduler Job %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.SetId(*resp.ID) + + return resourceArmSchedulerJobRead(d, meta) +} + +func resourceArmSchedulerJobRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).schedulerJobsClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + name := id.Path["jobs"] + resourceGroup := id.ResourceGroup + jobCollection := id.Path["jobCollections"] + + log.Printf("[DEBUG] Reading Scheduler Job %q (resource group %q)", name, resourceGroup) + + job, err := client.Get(ctx, resourceGroup, jobCollection, name) + if err != nil { + if utils.ResponseWasNotFound(job.Response) { + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on Scheduler Job %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + //standard properties + d.Set("name", name) + d.Set("resource_group_name", resourceGroup) + d.Set("job_collection_name", jobCollection) + + //check & get properties + properties := job.Properties + if properties != nil { + + //action + action := properties.Action + if action != nil { + actionType := strings.ToLower(string(action.Type)) + if strings.EqualFold(actionType, string(scheduler.HTTP)) || strings.EqualFold(actionType, string(scheduler.HTTPS)) { + if err := d.Set("action_web", flattenAzureArmSchedulerJobActionRequest(d, "action_web", action.Request)); err != nil { + return err + } + } + + //error action + if errorAction := action.ErrorAction; errorAction != nil { + if strings.EqualFold(actionType, string(scheduler.HTTP)) || strings.EqualFold(actionType, string(scheduler.HTTPS)) { + if err := d.Set("error_action_web", flattenAzureArmSchedulerJobActionRequest(d, "error_action_web", errorAction.Request)); err != nil { + return err + } + } + } + + //retry + if retry := action.RetryPolicy; retry != nil { + //if its not fixed we should not have a retry block + //api returns whatever casing it gets so do a case insensitive comparison + if strings.EqualFold(string(retry.RetryType), string(scheduler.Fixed)) { + if err := d.Set("retry", flattenAzureArmSchedulerJobActionRetry(retry)); err != nil { + return err + } + } + } + } + + //schedule + if recurrence := properties.Recurrence; recurrence != nil { + if err := d.Set("recurrence", flattenAzureArmSchedulerJobSchedule(recurrence)); err != nil { + return err + } + } + + if v := properties.StartTime; v != nil { + d.Set("start_time", (*v).Format(time.RFC3339)) + } + + //status && state + d.Set("state", properties.State) + } + + return nil +} + +func resourceArmSchedulerJobDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).schedulerJobsClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + name := id.Path["jobs"] + resourceGroup := id.ResourceGroup + jobCollection := id.Path["jobCollections"] + + log.Printf("[DEBUG] Deleting Scheduler Job %q (resource group %q)", name, resourceGroup) + + resp, err := client.Delete(ctx, resourceGroup, jobCollection, name) + if err != nil { + if !utils.ResponseWasNotFound(resp) { + return fmt.Errorf("Error issuing delete request for Scheduler Job %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + return nil +} + +func expandAzureArmSchedulerJobAction(d *schema.ResourceData, meta interface{}) *scheduler.JobAction { + + action := scheduler.JobAction{} + + //action + if b, ok := d.GetOk("action_web"); ok { + action.Request, action.Type = expandAzureArmSchedulerJobActionRequest(b, meta) + } + + //error action + if b, ok := d.GetOk("error_action_web"); ok { + action.ErrorAction = &scheduler.JobErrorAction{} + action.ErrorAction.Request, action.ErrorAction.Type = expandAzureArmSchedulerJobActionRequest(b, meta) + } + + //retry policy + if b, ok := d.GetOk("retry"); ok { + action.RetryPolicy = expandAzureArmSchedulerJobActionRetry(b) + } else { + action.RetryPolicy = &scheduler.RetryPolicy{ + RetryType: scheduler.None, + } + } + + return &action +} + +func expandAzureArmSchedulerJobActionRequest(b interface{}, meta interface{}) (*scheduler.HTTPRequest, scheduler.JobActionType) { + + block := b.([]interface{})[0].(map[string]interface{}) + + url := block["url"].(string) + + request := scheduler.HTTPRequest{ + URI: &url, + Method: utils.String(block["method"].(string)), + Headers: map[string]*string{}, + } + + // determine type from the url, the property validation must ensure this + // otherwise we need to worry about what happens if neither is true + var jobType scheduler.JobActionType + if strings.HasPrefix(strings.ToLower(url), "https://") { + jobType = scheduler.HTTPS + } else { + jobType = scheduler.HTTP + } + + //load headers + for k, v := range block["headers"].(map[string]interface{}) { + request.Headers[k] = utils.String(v.(string)) + } + + //only valid for a set + if v, ok := block["body"].(string); ok && v != "" { + request.Body = utils.String(block["body"].(string)) + } + + //authentications + if v, ok := block["authentication_basic"].([]interface{}); ok && len(v) > 0 { + b := v[0].(map[string]interface{}) + request.Authentication = &scheduler.BasicAuthentication{ + Type: scheduler.TypeBasic, + Username: utils.String(b["username"].(string)), + Password: utils.String(b["password"].(string)), + } + } + + if v, ok := block["authentication_certificate"].([]interface{}); ok && len(v) > 0 { + b := v[0].(map[string]interface{}) + request.Authentication = &scheduler.ClientCertAuthentication{ + Type: scheduler.TypeClientCertificate, + Pfx: utils.String(b["pfx"].(string)), + Password: utils.String(b["password"].(string)), + } + } + + if v, ok := block["authentication_active_directory"].([]interface{}); ok && len(v) > 0 { + b := v[0].(map[string]interface{}) + oauth := &scheduler.OAuthAuthentication{ + Type: scheduler.TypeActiveDirectoryOAuth, + Tenant: utils.String(b["tenant_id"].(string)), + ClientID: utils.String(b["client_id"].(string)), + Secret: utils.String(b["secret"].(string)), + } + + //default to the service Management Endpoint + if v, ok := b["audience"].(string); ok { + oauth.Audience = utils.String(v) + } else { + oauth.Audience = utils.String(meta.(*ArmClient).environment.ServiceManagementEndpoint) + } + + request.Authentication = oauth + } + + return &request, jobType +} + +func expandAzureArmSchedulerJobActionRetry(b interface{}) *scheduler.RetryPolicy { + block := b.([]interface{})[0].(map[string]interface{}) + retry := scheduler.RetryPolicy{ + RetryType: scheduler.Fixed, + } + + if v, ok := block["interval"].(string); ok && v != "" { + retry.RetryInterval = utils.String(v) + } + if v, ok := block["count"].(int); ok { + retry.RetryCount = utils.Int32(int32(v)) + } + + return &retry +} + +func expandAzureArmSchedulerJobRecurrence(b interface{}) *scheduler.JobRecurrence { + block := b.([]interface{})[0].(map[string]interface{}) + + recurrence := scheduler.JobRecurrence{ + Frequency: scheduler.RecurrenceFrequency(block["frequency"].(string)), + Interval: utils.Int32(int32(block["interval"].(int))), + } + if v, ok := block["count"].(int); ok { + recurrence.Count = utils.Int32(int32(v)) + } + if v, ok := block["end_time"].(string); ok && v != "" { + endTime, _ := time.Parse(time.RFC3339, v) //validated by schema + recurrence.EndTime = &date.Time{Time: endTime} + } + + schedule := scheduler.JobRecurrenceSchedule{} + if s, ok := block["minutes"].(*schema.Set); ok && s.Len() > 0 { + schedule.Minutes = set.ToSliceInt32P(s) + } + if s, ok := block["hours"].(*schema.Set); ok && s.Len() > 0 { + schedule.Hours = set.ToSliceInt32P(s) + } + + if s, ok := block["week_days"].(*schema.Set); ok && s.Len() > 0 { + var slice []scheduler.DayOfWeek + for _, m := range s.List() { + slice = append(slice, scheduler.DayOfWeek(m.(string))) + } + schedule.WeekDays = &slice + } + + if s, ok := block["month_days"].(*schema.Set); ok && s.Len() > 0 { + schedule.MonthDays = set.ToSliceInt32P(s) + } + if s, ok := block["monthly_occurrences"].(*schema.Set); ok && s.Len() > 0 { + var slice []scheduler.JobRecurrenceScheduleMonthlyOccurrence + for _, e := range s.List() { + b := e.(map[string]interface{}) + slice = append(slice, scheduler.JobRecurrenceScheduleMonthlyOccurrence{ + Day: scheduler.JobScheduleDay(b["day"].(string)), + Occurrence: utils.Int32(int32(b["occurrence"].(int))), + }) + } + schedule.MonthlyOccurrences = &slice + } + + // if non of these are set and we try and send out a empty JobRecurrenceSchedule block + // the API will not respond so kindly + if schedule.Minutes != nil || + schedule.Hours != nil || + schedule.WeekDays != nil || + schedule.MonthDays != nil || + schedule.MonthlyOccurrences != nil { + recurrence.Schedule = &schedule + } + return &recurrence +} + +// flatten (API --> terraform) + +func flattenAzureArmSchedulerJobActionRequest(d *schema.ResourceData, blockName string, request *scheduler.HTTPRequest) []interface{} { + + block := map[string]interface{}{} + + if v := request.URI; v != nil { + block["url"] = *v + } + if v := request.Method; v != nil { + block["method"] = *v + } + if v := request.Body; v != nil { + block["body"] = *v + } + + if v := request.Headers; v != nil { + headers := map[string]interface{}{} + for k, v := range v { + headers[k] = *v + } + + block["headers"] = headers + } + + if auth := request.Authentication; auth != nil { + + authBlock := map[string]interface{}{} + + if basic, ok := auth.AsBasicAuthentication(); ok { + block["authentication_basic"] = []interface{}{authBlock} + + if v := basic.Username; v != nil { + authBlock["username"] = *v + } + + //password is not returned so lets fetch it + if v, ok := d.GetOk(fmt.Sprintf("%s.0.authentication_basic.0.password", blockName)); ok { + authBlock["password"] = v.(string) + } + + } else if cert, ok := auth.AsClientCertAuthentication(); ok { + block["authentication_certificate"] = []interface{}{authBlock} + + if v := cert.CertificateThumbprint; v != nil { + authBlock["thumbprint"] = *v + } + if v := cert.CertificateExpirationDate; v != nil { + authBlock["expiration"] = (*v).Format(time.RFC3339) + } + if v := cert.CertificateSubjectName; v != nil { + authBlock["subject_name"] = *v + } + + //these properties not returned, so lets grab them + if v, ok := d.GetOk(fmt.Sprintf("%s.0.authentication_certificate.0.pfx", blockName)); ok { + authBlock["pfx"] = v.(string) + } + if v, ok := d.GetOk(fmt.Sprintf("%s.0.authentication_certificate.0.password", blockName)); ok { + authBlock["password"] = v.(string) + } + + } else if oauth, ok := auth.AsOAuthAuthentication(); ok { + block["authentication_active_directory"] = []interface{}{authBlock} + + if v := oauth.Audience; v != nil { + authBlock["audience"] = *v + } + if v := oauth.ClientID; v != nil { + authBlock["client_id"] = *v + } + if v := oauth.Tenant; v != nil { + authBlock["tenant_id"] = *v + } + + //secret is not returned + if v, ok := d.GetOk(fmt.Sprintf("%s.0.authentication_active_directory.0.secret", blockName)); ok { + authBlock["secret"] = v.(string) + } + } + } + + return []interface{}{block} +} + +func flattenAzureArmSchedulerJobActionRetry(retry *scheduler.RetryPolicy) []interface{} { + block := map[string]interface{}{} + + if v := retry.RetryInterval; v != nil { + block["interval"] = *v + } + if v := retry.RetryCount; v != nil { + block["count"] = *v + } + + return []interface{}{block} +} + +func flattenAzureArmSchedulerJobSchedule(recurrence *scheduler.JobRecurrence) []interface{} { + block := map[string]interface{}{} + + block["frequency"] = string(recurrence.Frequency) + + if v := recurrence.Interval; v != nil { + block["interval"] = *v + } + if v := recurrence.Count; v != nil { + block["count"] = *v + } + if v := recurrence.EndTime; v != nil { + block["end_time"] = (*v).Format(time.RFC3339) + } + + if schedule := recurrence.Schedule; schedule != nil { + + if v := schedule.Minutes; v != nil { + block["minutes"] = set.FromInt32Slice(*v) + } + if v := schedule.Hours; v != nil { + block["hours"] = set.FromInt32Slice(*v) + } + + if v := schedule.WeekDays; v != nil { + set := &schema.Set{F: schema.HashString} + for _, v := range *v { + set.Add(string(v)) + } + block["week_days"] = set + } + if v := schedule.MonthDays; v != nil { + block["month_days"] = set.FromInt32Slice(*v) + } + + if monthly := schedule.MonthlyOccurrences; monthly != nil { + set := &schema.Set{F: resourceAzureRMSchedulerJobMonthlyOccurrenceHash} + for _, e := range *monthly { + + m := map[string]interface{}{ + "day": string(e.Day), + } + + if v := e.Occurrence; v != nil { + m["occurrence"] = int(*v) + } + + set.Add(m) + } + block["monthly_occurrences"] = set + } + } + + return []interface{}{block} +} + +func resourceAzureRMSchedulerJobMonthlyOccurrenceHash(v interface{}) int { + var buf bytes.Buffer + + if m, ok := v.(map[string]interface{}); ok { + //day returned by azure is in a different case then the API constants + buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["day"].(string)))) + buf.WriteString(fmt.Sprintf("%d-", m["occurrence"].(int))) + } + + return hashcode.String(buf.String()) +} diff --git a/azurerm/resource_arm_scheduler_job_test.go b/azurerm/resource_arm_scheduler_job_test.go new file mode 100644 index 000000000000..ff5f64c1360a --- /dev/null +++ b/azurerm/resource_arm_scheduler_job_test.go @@ -0,0 +1,786 @@ +package azurerm + +import ( + "fmt" + "os" + "testing" + + "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 TestAccAzureRMSchedulerJob_web_basic(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_basic(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "http://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_put(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_put(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "http://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "put"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.body", "this is some text"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_authBasic(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_authBasic(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.authentication_basic.0.username", "login"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"action_web.0.authentication_basic.0.password"}, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_authCert(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_authCert(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.authentication_certificate.0.thumbprint", "42C107874FD0E4A9583292A2F1098E8FE4B2EDDA"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.authentication_certificate.0.subject_name", "CN=Terraform App Gateway, OU=Azure, O=Terraform Tests, S=Some-State, C=US"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "action_web.0.authentication_certificate.0.pfx", + "action_web.0.authentication_certificate.0.password", + }, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_authAd(t *testing.T) { + if os.Getenv(resource.TestEnvVar) == "" { + t.Skipf("Skipping since %q isn't set", resource.TestEnvVar) + return + } + + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + clientId := os.Getenv("ARM_CLIENT_ID") + tenantId := os.Getenv("ARM_TENANT_ID") + secret := os.Getenv("ARM_CLIENT_SECRET") + + env, err := testArmEnvironment() + if err != nil { + t.Fatalf("Error loading Environment: %+v", err) + return + } + + audience := env.ServiceManagementEndpoint + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_authAd(ri, testLocation(), tenantId, clientId, secret, audience), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.authentication_active_directory.0.tenant_id", tenantId), + resource.TestCheckResourceAttr(resourceName, "action_web.0.authentication_active_directory.0.client_id", clientId), + resource.TestCheckResourceAttrSet(resourceName, "action_web.0.authentication_active_directory.0.audience"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"action_web.0.authentication_active_directory.0.secret"}, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_retry(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_retry(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + resource.TestCheckResourceAttr(resourceName, "retry.0.interval", "00:05:00"), + resource.TestCheckResourceAttr(resourceName, "retry.0.count", "10"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_recurring(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_recurring(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.frequency", "minute"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.interval", "5"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.count", "10"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_recurringDaily(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_recurringDaily(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.frequency", "day"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.count", "100"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.hours.#", "2"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.minutes.#", "4"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_recurringWeekly(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_recurringWeekly(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.frequency", "week"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.count", "100"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.week_days.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_recurringMonthly(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_recurringMonthly(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.frequency", "month"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.count", "100"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.month_days.#", "4"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_recurringMonthlyOccurrences(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_recurringMonthlyOccurrences(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.frequency", "month"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.count", "100"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_occurrences.#", "3"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_occurrences.2181640481.day", "sunday"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_occurrences.2181640481.occurrence", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_occurrences.2956940195.day", "sunday"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_occurrences.2956940195.occurrence", "3"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_occurrences.679325150.day", "sunday"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_occurrences.679325150.occurrence", "-1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_errorAction(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_errorAction(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "get"), + resource.TestCheckResourceAttr(resourceName, "error_action_web.0.url", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, "error_action_web.0.method", "get"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMSchedulerJob_web_complete(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_complete(ri, testLocation(), "2019-07-07T07:07:07-07:00"), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "action_web.0.url", "http://example.com"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.method", "put"), + resource.TestCheckResourceAttr(resourceName, "action_web.0.body", "this is some text"), + resource.TestCheckResourceAttr(resourceName, "retry.0.interval", "00:05:00"), + resource.TestCheckResourceAttr(resourceName, "retry.0.count", "10"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.frequency", "month"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.count", "100"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.month_days.#", "4"), + resource.TestCheckResourceAttr(resourceName, "start_time", "2019-07-07T14:07:07Z"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckAzureRMSchedulerJobDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_scheduler_job.test" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + jobCollection := rs.Primary.Attributes["job_collection_name"] + + client := testAccProvider.Meta().(*ArmClient).schedulerJobsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, jobCollection, 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 testCheckAzureRMSchedulerJobExists(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"] + jobCollection := rs.Primary.Attributes["job_collection_name"] + + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Scheduler Job: %q", name) + } + + client := testAccProvider.Meta().(*ArmClient).schedulerJobsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, jobCollection, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Scheduler Job %q (resource group: %q) was not found: %+v", name, resourceGroup, err) + } + + return fmt.Errorf("Bad: Get on schedulerJobsClient: %+v", err) + } + + return nil + } +} + +func testAccAzureRMSchedulerJob_template(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_scheduler_job_collection" "test" { + name = "acctestRG-%[1]d-job_collection" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "standard" +} + +`, rInt, location) +} + +func testAccAzureRMSchedulerJob_web_basic(rInt int, location string) string { + //need a valid URL here otherwise on a slow connection job might fault before the test check + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + action_web { + url = "http://example.com" + method = "get" + } +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_put(rInt int, location string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "http://example.com" + method = "put" + body = "this is some text" + + headers = { + "Content-Type" = "text" + } + } +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_authBasic(rInt int, location string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "https://example.com" + method = "get" + + authentication_basic { + username = "login" + password = "apassword" + } + } +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_authCert(rInt int, location string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "https://example.com" + method = "get" + + authentication_certificate { + pfx = "${base64encode(file("testdata/application_gateway_test.pfx"))}" + password = "terraform" + } + } +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_authAd(rInt int, location, tenantId, clientId, secret, audience string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "https://example.com" + method = "get" + + authentication_active_directory { + tenant_id = "%s" + client_id = "%s" + secret = "%s" + audience = "%s" + } + } +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt, tenantId, clientId, secret, audience) +} + +func testAccAzureRMSchedulerJob_web_retry(rInt int, location string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "https://example.com" + method = "get" + } + + retry { + interval = "00:05:00" //5 min + count = 10 + } + +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_recurring(rInt int, location string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "https://example.com" + method = "get" + } + + recurrence { + frequency = "minute" + interval = 5 + count = 10 + } + +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_recurringDaily(rInt int, location string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "https://example.com" + method = "get" + } + + recurrence { + frequency = "day" + count = 100 + hours = [0,12] + minutes = [0,15,30,45] + } + +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_recurringWeekly(rInt int, location string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "https://example.com" + method = "get" + } + + recurrence { + frequency = "week" + count = 100 + week_days = ["Sunday", "Saturday"] + } + +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_recurringMonthly(rInt int, location string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "https://example.com" + method = "get" + } + + recurrence { + frequency = "month" + count = 100 + month_days = [-11,-1,1,11] + } + +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_recurringMonthlyOccurrences(rInt int, location string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "https://example.com" + method = "get" + } + + recurrence { + frequency = "month" + count = 100 + monthly_occurrences = [ + { + day = "sunday" + occurrence = 1 + }, + { + day = "sunday" + occurrence = 3 + }, + { + day = "sunday" + occurrence = -1 + } + ] + } + +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_errorAction(rInt int, location string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "https://example.com" + method = "get" + } + + error_action_web { + url = "https://example.com" + method = "get" + } + +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_web_complete(rInt int, location, time string) string { + return fmt.Sprintf(`%s +resource "azurerm_scheduler_job" "test" { + name = "acctestRG-%d-job" + resource_group_name = "${azurerm_resource_group.test.name}" + job_collection_name = "${azurerm_scheduler_job_collection.test.name}" + + + action_web { + url = "http://example.com" + method = "put" + body = "this is some text" + + headers = { + "Content-Type" = "text" + } + } + + retry { + interval = "00:05:00" //5 min + count = 10 + } + + recurrence { + frequency = "month" + count = 100 + month_days = [-11,-1,1,11] + } + + start_time = "%s" + +}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt, time) +} diff --git a/examples/scheduler-jobs/main.tf b/examples/scheduler-jobs/main.tf index 6b0e6ee689b7..f277a945b96a 100644 --- a/examples/scheduler-jobs/main.tf +++ b/examples/scheduler-jobs/main.tf @@ -3,16 +3,169 @@ resource "azurerm_resource_group" "rg" { location = "${var.resource_group_location}" } -resource "azurerm_scheduler_job_collection" "jobs" { - name = "example_job_collection" +resource "azurerm_scheduler_job_collection" "jc" { + name = "tfex-scheduler-job-collection" location = "${azurerm_resource_group.rg.location}" resource_group_name = "${azurerm_resource_group.rg.name}" - sku = "free" + sku = "standard" state = "enabled" quota { max_job_count = 5 - max_recurrence_interval = 24 - max_recurrence_frequency = "hour" + max_recurrence_interval = 5 + max_recurrence_frequency = "minute" } } + +resource "azurerm_scheduler_job" "web-once-now" { + name = "tfex-web-once-now" + resource_group_name = "${azurerm_resource_group.rg.name}" + job_collection_name = "${azurerm_scheduler_job_collection.jc.name}" + + state = "enabled" + + action_web { + url = "http://example.com" + method = "get" + } + + retry { + interval = "00:05:00" //5 min + count = 4 + } + + //times in the past start immediatly and run once, + start_time = "1987-07-07T07:07:07-07:00" +} + +resource "azurerm_scheduler_job" "web-recurring" { + name = "tfex-web-recurring" + resource_group_name = "${azurerm_resource_group.rg.name}" + job_collection_name = "${azurerm_scheduler_job_collection.jc.name}" + + action_web { + url = "http://example.com" + method = "put" + body = "this is some text" + + headers = { + Content-Type = "text" + } + } + + retry { + interval = "00:05:00" //5 min + count = 4 + } + + recurrence { + frequency = "minute" + interval = 10 + count = 10 //recurring counts start in past + } + + start_time = "2019-07-07T07:07:07-07:00" +} + +resource "azurerm_scheduler_job" "web-recurring_daily-auth_basic" { + name = "tfex-web-recurring_daily-auth_basic" + resource_group_name = "${azurerm_resource_group.rg.name}" + job_collection_name = "${azurerm_scheduler_job_collection.jc.name}" + + state = "enabled" + + action_web { + url = "https://example.com" + method = "get" + + authentication_basic { + username = "login" + password = "apassword" + } + } + + recurrence { + frequency = "day" + count = 1000 + hours = [0, 12] + minutes = [0, 15, 30, 45] + } + + start_time = "2019-07-07T07:07:07-07:00" +} + +resource "azurerm_scheduler_job" "web-recurring_weekly-auth_cert" { + name = "tfex-web-recurring_weekly-auth_cert" + resource_group_name = "${azurerm_resource_group.rg.name}" + job_collection_name = "${azurerm_scheduler_job_collection.jc.name}" + + state = "enabled" + + action_web { + url = "https://example.com" + method = "get" + + authentication_certificate { + pfx = "${base64encode(file("../../azurerm/testdata/application_gateway_test.pfx"))}" + password = "terraform" + } + } + + recurrence { + frequency = "week" + count = 1000 + week_days = ["Sunday", "Saturday"] + } + + start_time = "2019-07-07T07:07:07-07:00" +} + +resource "azurerm_scheduler_job" "web-recurring_monthly-error_action" { + name = "tfex-web-recurring_monthly-auth_ad" + resource_group_name = "${azurerm_resource_group.rg.name}" + job_collection_name = "${azurerm_scheduler_job_collection.jc.name}" + + state = "enabled" + + action_web { + url = "http://example.com" + method = "get" + } + + error_action_web { + url = "https://example.com" + method = "put" + body = "The job failed" + + headers = { + Content-Type = "text" + } + + authentication_basic { + username = "login" + password = "apassword" + } + } + + recurrence { + frequency = "month" + count = 1000 + + monthly_occurrences = [ + { + day = "Sunday" + occurrence = 1 + }, + { + day = "Sunday" + occurrence = 3 + }, + { + day = "Sunday" + occurrence = -1 + }, + ] + } + + start_time = "2019-07-07T07:07:07-07:00" +} diff --git a/examples/scheduler-jobs/outputs.tf b/examples/scheduler-jobs/outputs.tf index c97be3ee461d..f8ed3606c353 100644 --- a/examples/scheduler-jobs/outputs.tf +++ b/examples/scheduler-jobs/outputs.tf @@ -1,3 +1,8 @@ output "job_collection-id" { - value = "${azurerm_scheduler_job_collection.jobs.id}" + value = "${azurerm_scheduler_job_collection.jc.id}" } + +output "job-web-once-url" { + value = "${azurerm_scheduler_job.web-once-now.action_web.0.url}" +} + diff --git a/website/azurerm.erb b/website/azurerm.erb index 6040b3c79166..63e393803a17 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -740,10 +740,15 @@ > Scheduler Resources + > diff --git a/website/docs/d/scheduler_job_collection.html.markdown b/website/docs/d/scheduler_job_collection.html.markdown index 83727ba78864..896c2c7ed8aa 100644 --- a/website/docs/d/scheduler_job_collection.html.markdown +++ b/website/docs/d/scheduler_job_collection.html.markdown @@ -1,7 +1,7 @@ --- layout: "azurerm" page_title: "Azure Resource Manager: azurerm_scheduler_job_collection" -sidebar_current: "docs-azurerm-datasource-scheduler_job_collection" +sidebar_current: "docs-azurerm-datasource-scheduler-job-collection" description: |- Get information about the specified scheduler job collection. --- diff --git a/website/docs/r/scheduler_job.html.markdown b/website/docs/r/scheduler_job.html.markdown new file mode 100644 index 000000000000..94b5c4ee825a --- /dev/null +++ b/website/docs/r/scheduler_job.html.markdown @@ -0,0 +1,219 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_scheduler_job" +sidebar_current: "docs-azurerm-resource-scheduler-job-x +description: |- + Manages a Scheduler Job. +--- + +# azurerm_scheduler_job + +Manages a Scheduler Job. + +## Example Usage (single web get now) + +```hcl +resource "azurerm_scheduler_job" "web-once-now" { + name = "tfex-web-once-now" + resource_group_name = "${azurerm_resource_group.rg.name}" + job_collection_name = "${azurerm_scheduler_job_collection.jc.name}" + + state = "enabled" //re-enable it each run + + action_web { + url = "http://this.url.fails" //defaults to get + } + + //default start time is now +} +``` + +## Example Usage (recurring daily with retry and basic authentication) + +```hcl +resource "azurerm_scheduler_job" "web-recurring-daily" { + name = "tfex-web-recurring-daily" + resource_group_name = "${azurerm_resource_group.rg.name}" + job_collection_name = "${azurerm_scheduler_job_collection.jc.name}" + + action_web { + url = "https://this.url.fails" + method = "put" + body = "this is some text" + + headers = { + Content-Type = "text" + } + + authentication_basic { + username = "login" + password = "apassword" + } + } + + retry { + interval = "00:05:00" //retry every 5 min + count = 10 //a maximum or 10 times + } + + recurrence { + frequency = "day" + count = 1000 + hours = [0,12] //run every 12 hours + minutes = [0,15,30,45] //4 times an hour + } + + start_time = "2018-07-07T07:07:07-07:00" +} +``` + +## Example Usage (recurring monthly with an error action and client certificate authentication) + +```hcl +resource "azurerm_scheduler_job" "web-recurring-daily" { + name = "tfex-web-recurring-daily" + resource_group_name = "${azurerm_resource_group.rg.name}" + job_collection_name = "${azurerm_scheduler_job_collection.jc.name}" + + action_web { + url = "https://this.url.fails" + authentication_certificate { + pfx = "${base64encode(file("your_cert.pfx"))}" + password = "cert_password" + } + } + + error_action_web { + url = "https://this.url.fails" + method = "put" + body = "The job failed" + + headers = { + "Content-Type" = "text" + } + + authentication_basic { + username = "login" + password = "apassword" + } + } + + recurrence { + frequency = "monthly" + count = 1000 + monthly_occurrences = [ + { //first sunday + day = "Sunday" + occurrence = 1 + }, + { //third sunday + day = "Sunday" + occurrence = 3 + }, + { //last sunday + day = "Sunday" + occurrence = -1 + } + ] + } + + start_time = "2018-07-07T07:07:07-07:00" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Scheduler Job. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which to create the Scheduler Job. Changing this forces a new resource to be created. + +* `job_collection_name` - (Required) Specifies the name of the Scheduler Job Collection in which the Job should exist. Changing this forces a new resource to be created. + +* `action_web` - (Required) A `action_web` block defining the job action as described below. Note this is identical to an `error_action_web` block. + +* `error_action_web` - (Optional) A `error_action_web` block defining the action to take on an error as described below. Note this is identical to an `action_web` block. + +* `retry` - (Optional) A `retry` block defining how to retry as described below. + +* `recurrence` - (Optional) A `recurrence` block defining a job occurrence schedule. + +* `start_time` - (Optional) The time the first instance of the job is to start running at. + +* `state` - (Optional) The sets or gets the current state of the job. Can be set to either `Enabled` or `Completed` + + +`web_action` & `error_web_action` block supports the following: + +* `url` - (Required) Specifies the URL of the web request. Must be HTTPS for authenticated requests. +* `method` - (Optional) Specifies the method of the request. Defaults to `Get` and must be one of `Get`, `Put`, `Post`, `Delete`. +* `body` - (Optional) Specifies the request body. +* `headers` - (Optional) A map specifying the headers sent with the request. +* `authentication_basic` - (Optional) An `authentication_active_directory` block which defines the Active Directory oauth configuration to use. +* `authentication_certificate` - (Optional) An `authentication_certificate` block which defines the client certificate information to be use. +* `authentication_active_directory` - (Optional) An `authentication_active_directory` block which defines the OAUTH Active Directory information to use. + + +`authentication_basic` block supports the following: + +* `username` - (Required) Specifies the username to use. +* `password` - (Required) Specifies the password to use. + + +`authentication_certificate` block supports the following: + +* `pfx` - (Required) Specifies the pfx certificate in base-64 format. +* `password` - (Required) Specifies the certificate password. + + +`authentication_active_directory` block supports the following: + +* `client_id` - (Required) Specifies the client ID to use. +* `tenant_id` - (Required) Specifies the tenant ID to use. +* `client_secret` - (Required) Specifies the secret to use. +* `audience` - (Optional) Specifies the audience. + + +`retry` block supports the following: + +* `interval` - (Required) Specifies the duration between retries. +* `count` - (Required) Specifies the number of times a retry should be attempted. + +`recurrence` block supports the following: + +* `frequency` - (Required) Specifies the frequency of recurrence. Must be one of `Minute`, `Hour`, `Day`, `Week`, `Month`. +* `interval` - (Optional) Specifies the interval between executions. Defaults to `1`. +* `count` - (Optional) Specifies the maximum number of times that the job should run. +* `end_time` - (Optional) Specifies the time at which the job will cease running. Must be less then 500 days into the future. +* `minutes` - (Optional) Specifies the minutes of the hour that the job should execute at. Must be between `0` and `59` +* `hours` - (Optional) Specifies the hours of the day that the job should execute at. Must be between `0` and `23` +* `week_days` - (Optional) Specifies the days of the week that the job should execute on. Must be one of `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, `Saturday`, `Sunday`. Only applies when `Week` is used for frequency. +* `month_days` - (Optional) Specifies the days of the month that the job should execute on. Must be non zero and between `-1` and `31`. Only applies when `Month` is used for frequency. +* `monthly_occurrences` - (Optional) Specifies specific monthly occurrences like "last sunday of the month" with `monthly_occurrences` blocks. Only applies when `Month` is used for frequency. + +`monthly_occurrences` block supports the following: + +* `day` - (Optional) Specifies the day of the week that the job should execute on. Must be one of one of `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, `Saturday`, `Sunday`. +* `occurrence` - (Optional) Specifies the week the job should run on. For example `1` for the first week, `-1` for the last week of the month. Must be between `-5` and `5`. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Scheduler Job ID. + +`authentication_certificate` block exports the following: + +* `thumbprint` - (Computed) The certificate thumbprint. +* `expiration` - (Computed) The certificate expiration date. +* `subject_name` - (Computed) The certificate's certificate subject name. + +## Import + +Scheduler Job can be imported using a `resource id`, e.g. + +```shell +terraform import azurerm_scheduler_job.job1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Scheduler/jobCollections/jobCollection1/jobs/job1 +``` diff --git a/website/docs/r/scheduler_job_collection.html.markdown b/website/docs/r/scheduler_job_collection.html.markdown index 2fdcf743a174..a717ce2865c2 100644 --- a/website/docs/r/scheduler_job_collection.html.markdown +++ b/website/docs/r/scheduler_job_collection.html.markdown @@ -1,14 +1,14 @@ --- layout: "azurerm" page_title: "Azure Resource Manager: azurerm_scheduler_job_collection" -sidebar_current: "docs-azurerm-resource-scheduler_job_collection" +sidebar_current: "docs-azurerm-resource-scheduler-job-collection" description: |- - Create an Scheduler Job Collection. + Manages a Scheduler Job Collection. --- # azurerm_scheduler_job_collection -Create an Scheduler Job Collection. +Manages a Scheduler Job Collection. ## Example Usage