Skip to content

Commit

Permalink
azurerm_automation_job_schedule needs to be recreated after `azurer…
Browse files Browse the repository at this point in the history
…m_automation_runbook` is updated (#7555)

Fix: #7130

Local test the issue scenario with pass.

Job_schedule is a link from runbook to schedule. Once runbook has been updated, all its related job_schedule will update all linked job schedule id.
First terraform apply, one change to update runbook, terraform apply with success.
Second terraform apply, one create to create job_schedule, (Because job schedule id has been updated, the job schedule reading from its elder job schedule id doesn't exist any more. Here we delete its linked job schedule and recreate this job schedule ), terraform apply with success.
Co-authored-by: kt <[email protected]>
  • Loading branch information
yupwei68 and katbyte authored Jul 24, 2020
1 parent 463e1fc commit de6606d
Show file tree
Hide file tree
Showing 4 changed files with 424 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,17 @@ func resourceArmAutomationJobScheduleCreate(d *schema.ResourceData, meta interfa

log.Printf("[INFO] preparing arguments for AzureRM Automation Job Schedule creation.")

jobScheduleUUID := uuid.NewV4()
if jobScheduleID, ok := d.GetOk("job_schedule_id"); ok {
jobScheduleUUID = uuid.FromStringOrNil(jobScheduleID.(string))
}

resourceGroup := 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)

jobScheduleUUID := uuid.NewV4()
if jobScheduleID, ok := d.GetOk("job_schedule_id"); ok {
jobScheduleUUID = uuid.FromStringOrNil(jobScheduleID.(string))
}

if d.IsNewResource() {
existing, err := client.Get(ctx, resourceGroup, accountName, jobScheduleUUID)
if err != nil {
Expand All @@ -126,6 +126,29 @@ func resourceArmAutomationJobScheduleCreate(d *schema.ResourceData, meta interfa
}
}

//fix issue: https://github.com/terraform-providers/terraform-provider-azurerm/issues/7130
//When the runbook has some updates, it'll update all related job schedule id, so the elder job schedule will not exist
//We need to delete the job schedule id if exists to recreate the job schedule
for jsIterator, err := client.ListByAutomationAccountComplete(ctx, resourceGroup, accountName, ""); jsIterator.NotDone(); err = jsIterator.NextWithContext(ctx) {
if err != nil {
return fmt.Errorf("loading Automation Account %q Job Schedule List: %+v", accountName, err)
}
if props := jsIterator.Value().JobScheduleProperties; props != nil {
if props.Schedule.Name != nil && *props.Schedule.Name == scheduleName && props.Runbook.Name != nil && *props.Runbook.Name == runbookName {
if jsIterator.Value().JobScheduleID == nil || *jsIterator.Value().JobScheduleID == "" {
return fmt.Errorf("job schedule Id is nil or empty listed by Automation Account %q Job Schedule List: %+v", accountName, err)
}
jsId, err := uuid.FromString(*jsIterator.Value().JobScheduleID)
if err != nil {
return fmt.Errorf("parsing job schedule Id listed by Automation Account %q Job Schedule List:%v", accountName, err)
}
if _, err := client.Delete(ctx, resourceGroup, accountName, jsId); err != nil {
return fmt.Errorf("deleting job schedule Id listed by Automation Account %q Job Schedule List:%v", accountName, err)
}
}
}
}

parameters := automation.JobScheduleCreateParameters{
JobScheduleCreateProperties: &automation.JobScheduleCreateProperties{
Schedule: &automation.ScheduleAssociationProperty{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import (
"github.com/Azure/azure-sdk-for-go/services/automation/mgmt/2015-10-31/automation"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
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/suppress"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/automation/helper"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
Expand Down Expand Up @@ -94,6 +96,8 @@ func resourceArmAutomationRunbook() *schema.Resource {
ValidateFunc: validation.StringIsNotEmpty,
},

"job_schedule": helper.JobScheduleSchema(),

"publish_content_link": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -139,6 +143,7 @@ func resourceArmAutomationRunbook() *schema.Resource {

func resourceArmAutomationRunbookCreateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Automation.RunbookClient
jsClient := meta.(*clients.Client).Automation.JobScheduleClient
ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()

Expand Down Expand Up @@ -217,11 +222,41 @@ func resourceArmAutomationRunbookCreateUpdate(d *schema.ResourceData, meta inter

d.SetId(*read.ID)

for jsIterator, err := jsClient.ListByAutomationAccountComplete(ctx, resGroup, accName, ""); jsIterator.NotDone(); err = jsIterator.NextWithContext(ctx) {
if err != nil {
return fmt.Errorf("loading Automation Account %q Job Schedule List: %+v", accName, err)
}
if props := jsIterator.Value().JobScheduleProperties; props != nil {
if props.Runbook.Name != nil && *props.Runbook.Name == name {
if jsIterator.Value().JobScheduleID == nil || *jsIterator.Value().JobScheduleID == "" {
return fmt.Errorf("job schedule Id is nil or empty listed by Automation Account %q Job Schedule List: %+v", accName, err)
}
jsId, err := uuid.FromString(*jsIterator.Value().JobScheduleID)
if err != nil {
return fmt.Errorf("parsing job schedule Id listed by Automation Account %q Job Schedule List:%v", accName, err)
}
if _, err := jsClient.Delete(ctx, resGroup, accName, jsId); err != nil {
return fmt.Errorf("deleting job schedule Id listed by Automation Account %q Job Schedule List:%v", accName, err)
}
}
}
}

if v, ok := d.GetOk("job_schedule"); ok {
jsMap := helper.ExpandAutomationJobSchedule(v.(*schema.Set).List(), name)
for jsuuid, js := range jsMap {
if _, err := jsClient.Create(ctx, resGroup, accName, jsuuid, js); err != nil {
return fmt.Errorf("creating Automation Runbook %q Job Schedules (Account %q / Resource Group %q): %+v", name, accName, resGroup, err)
}
}
}

return resourceArmAutomationRunbookRead(d, meta)
}

func resourceArmAutomationRunbookRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Automation.RunbookClient
jsClient := meta.(*clients.Client).Automation.JobScheduleClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

Expand Down Expand Up @@ -277,6 +312,30 @@ func resourceArmAutomationRunbookRead(d *schema.ResourceData, meta interface{})
}
}

jsMap := make(map[uuid.UUID]automation.JobScheduleProperties)
for jsIterator, err := jsClient.ListByAutomationAccountComplete(ctx, resGroup, accName, ""); jsIterator.NotDone(); err = jsIterator.NextWithContext(ctx) {
if err != nil {
return fmt.Errorf("loading Automation Account %q Job Schedule List: %+v", accName, err)
}
if props := jsIterator.Value().JobScheduleProperties; props != nil {
if props.Runbook.Name != nil && *props.Runbook.Name == name {
if jsIterator.Value().JobScheduleID == nil || *jsIterator.Value().JobScheduleID == "" {
return fmt.Errorf("job schedule Id is nil or empty listed by Automation Account %q Job Schedule List: %+v", accName, err)
}
jsId, err := uuid.FromString(*jsIterator.Value().JobScheduleID)
if err != nil {
return fmt.Errorf("parsing job schedule Id listed by Automation Account %q Job Schedule List:%v", accName, err)
}
jsMap[jsId] = *props
}
}
}

jobSchedule := helper.FlattenAutomationJobSchedule(jsMap)
if err := d.Set("job_schedule", jobSchedule); err != nil {
return fmt.Errorf("setting `job_schedule`: %+v", err)
}

if t := resp.Tags; t != nil {
return tags.FlattenAndSet(d, t)
}
Expand Down
144 changes: 144 additions & 0 deletions azurerm/internal/services/automation/helper/automation_job_schedule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package helper

import (
"bytes"
"fmt"
"strings"

"github.com/Azure/azure-sdk-for-go/services/automation/mgmt/2015-10-31/automation"
"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
"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/utils"
)

func JobScheduleSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"schedule_name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: azure.ValidateAutomationScheduleName(),
},

"parameters": {
Type: schema.TypeMap,
Optional: 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,
},

"job_schedule_id": {
Type: schema.TypeString,
Computed: true,
},
},
},
Set: resourceAutomationJobScheduleHash,
}
}

func ExpandAutomationJobSchedule(input []interface{}, runBookName string) map[uuid.UUID]automation.JobScheduleCreateParameters {
res := make(map[uuid.UUID]automation.JobScheduleCreateParameters)
if len(input) == 0 || input[0] == nil {
return res
}

for _, v := range input {
js := v.(map[string]interface{})
jobScheduleCreateParameters := automation.JobScheduleCreateParameters{
JobScheduleCreateProperties: &automation.JobScheduleCreateProperties{
Schedule: &automation.ScheduleAssociationProperty{
Name: utils.String(js["schedule_name"].(string)),
},
Runbook: &automation.RunbookAssociationProperty{
Name: utils.String(runBookName),
},
},
}

if v, ok := js["parameters"]; ok {
jsParameters := make(map[string]*string)
for k, v := range v.(map[string]interface{}) {
value := v.(string)
jsParameters[k] = &value
}
jobScheduleCreateParameters.JobScheduleCreateProperties.Parameters = jsParameters
}

if v, ok := js["run_on"]; ok && v.(string) != "" {
value := v.(string)
jobScheduleCreateParameters.JobScheduleCreateProperties.RunOn = &value
}
jobScheduleUUID := uuid.NewV4()
res[jobScheduleUUID] = jobScheduleCreateParameters
}

return res
}

func FlattenAutomationJobSchedule(jsMap map[uuid.UUID]automation.JobScheduleProperties) *schema.Set {
res := &schema.Set{
F: resourceAutomationJobScheduleHash,
}
for jsId, js := range jsMap {
var scheduleName, runOn string
if js.Schedule.Name != nil {
scheduleName = *js.Schedule.Name
}

if js.RunOn != nil {
runOn = *js.RunOn
}

res.Add(map[string]interface{}{
"schedule_name": scheduleName,
"parameters": utils.FlattenMapStringPtrString(js.Parameters),
"run_on": runOn,
"job_schedule_id": jsId.String(),
})
}

return res
}

func resourceAutomationJobScheduleHash(v interface{}) int {
var buf bytes.Buffer

if m, ok := v.(automation.JobScheduleProperties); ok {
var scheduleName, runOn string
if m.Schedule.Name != nil {
scheduleName = *m.Schedule.Name
}

if m.RunOn != nil {
runOn = *m.RunOn
}

buf.WriteString(fmt.Sprintf("%s-%s-%s-%s", scheduleName, utils.FlattenMapStringPtrString(m.Parameters), runOn, *m.JobScheduleID))
}

return hashcode.String(buf.String())
}
Loading

0 comments on commit de6606d

Please sign in to comment.