Skip to content

Commit 98ba57d

Browse files
authored
automation: split automation_module CreateUpdate into separate Create and Update (#31952)
1 parent 6412dae commit 98ba57d

File tree

1 file changed

+112
-63
lines changed

1 file changed

+112
-63
lines changed

internal/services/automation/automation_module_resource.go

Lines changed: 112 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package automation
55

66
import (
7+
"context"
78
"errors"
89
"fmt"
910
"log"
@@ -22,9 +23,9 @@ import (
2223

2324
func resourceAutomationModule() *pluginsdk.Resource {
2425
return &pluginsdk.Resource{
25-
Create: resourceAutomationModuleCreateUpdate,
26+
Create: resourceAutomationModuleCreate,
2627
Read: resourceAutomationModuleRead,
27-
Update: resourceAutomationModuleCreateUpdate,
28+
Update: resourceAutomationModuleUpdate,
2829
Delete: resourceAutomationModuleDelete,
2930

3031
Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error {
@@ -91,29 +92,27 @@ func resourceAutomationModule() *pluginsdk.Resource {
9192
}
9293
}
9394

94-
func resourceAutomationModuleCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error {
95+
func resourceAutomationModuleCreate(d *pluginsdk.ResourceData, meta interface{}) error {
9596
client := meta.(*clients.Client).Automation.Module
9697
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
97-
ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d)
98+
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
9899
defer cancel()
99100

100101
log.Printf("[INFO] preparing arguments for AzureRM Automation Module creation.")
101102

102103
id := module.NewModuleID(subscriptionId, d.Get("resource_group_name").(string), d.Get("automation_account_name").(string), d.Get("name").(string))
103104

104-
if d.IsNewResource() {
105-
existing, err := client.Get(ctx, id)
106-
if err != nil {
107-
if !response.WasNotFound(existing.HttpResponse) {
108-
return fmt.Errorf("checking for presence of existing %s: %s", id, err)
109-
}
105+
existing, err := client.Get(ctx, id)
106+
if err != nil {
107+
if !response.WasNotFound(existing.HttpResponse) {
108+
return fmt.Errorf("checking for presence of existing %s: %s", id, err)
110109
}
110+
}
111111

112-
// for existing global module do update instead of raising ImportAsExistsError
113-
isGlobal := existing.Model != nil && existing.Model.Properties != nil && existing.Model.Properties.IsGlobal != nil && *existing.Model.Properties.IsGlobal
114-
if !response.WasNotFound(existing.HttpResponse) && !isGlobal {
115-
return tf.ImportAsExistsError("azurerm_automation_module", id.ID())
116-
}
112+
// for existing global module do update instead of raising ImportAsExistsError
113+
isGlobal := existing.Model != nil && existing.Model.Properties != nil && existing.Model.Properties.IsGlobal != nil && *existing.Model.Properties.IsGlobal
114+
if !response.WasNotFound(existing.HttpResponse) && !isGlobal {
115+
return tf.ImportAsExistsError("azurerm_automation_module", id.ID())
117116
}
118117

119118
parameters := module.ModuleCreateOrUpdateParameters{
@@ -123,63 +122,59 @@ func resourceAutomationModuleCreateUpdate(d *pluginsdk.ResourceData, meta interf
123122
}
124123

125124
if _, err := client.CreateOrUpdate(ctx, id, parameters); err != nil {
125+
return fmt.Errorf("creating %s: %+v", id, err)
126+
}
127+
128+
if err := waitForModuleProvisioningCompletion(ctx, client, id, d.Timeout(pluginsdk.TimeoutCreate)); err != nil {
126129
return err
127130
}
128131

129-
// the API returns 'done' but it's not actually finished provisioning yet
130-
// tracking issue: https://github.com/Azure/azure-rest-api-specs/pull/25435
131-
stateConf := &pluginsdk.StateChangeConf{
132-
Pending: []string{
133-
string(module.ModuleProvisioningStateActivitiesStored),
134-
string(module.ModuleProvisioningStateConnectionTypeImported),
135-
string(module.ModuleProvisioningStateContentDownloaded),
136-
string(module.ModuleProvisioningStateContentRetrieved),
137-
string(module.ModuleProvisioningStateContentStored),
138-
string(module.ModuleProvisioningStateContentValidated),
139-
string(module.ModuleProvisioningStateCreated),
140-
string(module.ModuleProvisioningStateCreating),
141-
string(module.ModuleProvisioningStateModuleDataStored),
142-
string(module.ModuleProvisioningStateModuleImportRunbookComplete),
143-
string(module.ModuleProvisioningStateRunningImportModuleRunbook),
144-
string(module.ModuleProvisioningStateStartingImportModuleRunbook),
145-
string(module.ModuleProvisioningStateUpdating),
146-
},
147-
Target: []string{
148-
string(module.ModuleProvisioningStateSucceeded),
149-
},
150-
MinTimeout: 30 * time.Second,
151-
Refresh: func() (interface{}, string, error) {
152-
resp, err2 := client.Get(ctx, id)
153-
if err2 != nil {
154-
return resp, "Error", fmt.Errorf("retrieving %s: %+v", id, err2)
155-
}
132+
d.SetId(id.ID())
156133

157-
provisioningState := "Unknown"
158-
if model := resp.Model; model != nil {
159-
if props := model.Properties; props != nil {
160-
if props.ProvisioningState != nil {
161-
provisioningState = string(*props.ProvisioningState)
162-
}
163-
if props.Error != nil && props.Error.Message != nil && *props.Error.Message != "" {
164-
return resp, provisioningState, errors.New(*props.Error.Message)
165-
}
166-
return resp, provisioningState, nil
167-
}
168-
}
169-
return resp, provisioningState, nil
170-
},
134+
return resourceAutomationModuleRead(d, meta)
135+
}
136+
137+
func resourceAutomationModuleUpdate(d *pluginsdk.ResourceData, meta interface{}) error {
138+
client := meta.(*clients.Client).Automation.Module
139+
ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d)
140+
defer cancel()
141+
142+
id, err := module.ParseModuleID(d.Id())
143+
if err != nil {
144+
return err
171145
}
172-
if d.IsNewResource() {
173-
stateConf.Timeout = d.Timeout(pluginsdk.TimeoutCreate)
174-
} else {
175-
stateConf.Timeout = d.Timeout(pluginsdk.TimeoutUpdate)
146+
147+
existing, err := client.Get(ctx, *id)
148+
if err != nil {
149+
return fmt.Errorf("retrieving existing %s: %+v", *id, err)
176150
}
177151

178-
if _, err := stateConf.WaitForStateContext(ctx); err != nil {
179-
return fmt.Errorf("waiting for %s to finish provisioning: %+v", id, err)
152+
// Start from the existing content link so that fields managed via
153+
// ignore_changes retain their server-side value.
154+
var contentLink *module.ContentLink
155+
if existing.Model != nil && existing.Model.Properties != nil {
156+
contentLink = existing.Model.Properties.ContentLink
180157
}
181158

182-
d.SetId(id.ID())
159+
if d.HasChange("module_link") {
160+
expanded := expandModuleLink(d)
161+
contentLink = &expanded
162+
}
163+
164+
parameters := module.ModuleUpdateParameters{
165+
Name: &id.ModuleName,
166+
Properties: &module.ModuleUpdateProperties{
167+
ContentLink: contentLink,
168+
},
169+
}
170+
171+
if _, err := client.Update(ctx, *id, parameters); err != nil {
172+
return fmt.Errorf("updating %s: %+v", *id, err)
173+
}
174+
175+
if err := waitForModuleProvisioningCompletion(ctx, client, *id, d.Timeout(pluginsdk.TimeoutUpdate)); err != nil {
176+
return err
177+
}
183178

184179
return resourceAutomationModuleRead(d, meta)
185180
}
@@ -255,3 +250,57 @@ func expandModuleLink(d *pluginsdk.ResourceData) module.ContentLink {
255250
Uri: &uri,
256251
}
257252
}
253+
254+
// waitForModuleProvisioningCompletion polls the module until it reaches the Succeeded
255+
// provisioning state. The API returns 'done' before provisioning is actually complete.
256+
// Tracking issue: https://github.com/Azure/azure-rest-api-specs/pull/25435
257+
func waitForModuleProvisioningCompletion(ctx context.Context, client *module.ModuleClient, id module.ModuleId, timeout time.Duration) error {
258+
stateConf := &pluginsdk.StateChangeConf{
259+
Pending: []string{
260+
string(module.ModuleProvisioningStateActivitiesStored),
261+
string(module.ModuleProvisioningStateConnectionTypeImported),
262+
string(module.ModuleProvisioningStateContentDownloaded),
263+
string(module.ModuleProvisioningStateContentRetrieved),
264+
string(module.ModuleProvisioningStateContentStored),
265+
string(module.ModuleProvisioningStateContentValidated),
266+
string(module.ModuleProvisioningStateCreated),
267+
string(module.ModuleProvisioningStateCreating),
268+
string(module.ModuleProvisioningStateModuleDataStored),
269+
string(module.ModuleProvisioningStateModuleImportRunbookComplete),
270+
string(module.ModuleProvisioningStateRunningImportModuleRunbook),
271+
string(module.ModuleProvisioningStateStartingImportModuleRunbook),
272+
string(module.ModuleProvisioningStateUpdating),
273+
},
274+
Target: []string{
275+
string(module.ModuleProvisioningStateSucceeded),
276+
},
277+
MinTimeout: 30 * time.Second,
278+
Timeout: timeout,
279+
Refresh: func() (interface{}, string, error) {
280+
resp, err := client.Get(ctx, id)
281+
if err != nil {
282+
return resp, "Error", fmt.Errorf("retrieving %s: %+v", id, err)
283+
}
284+
285+
provisioningState := "Unknown"
286+
if model := resp.Model; model != nil {
287+
if props := model.Properties; props != nil {
288+
if props.ProvisioningState != nil {
289+
provisioningState = string(*props.ProvisioningState)
290+
}
291+
if props.Error != nil && props.Error.Message != nil && *props.Error.Message != "" {
292+
return resp, provisioningState, errors.New(*props.Error.Message)
293+
}
294+
return resp, provisioningState, nil
295+
}
296+
}
297+
return resp, provisioningState, nil
298+
},
299+
}
300+
301+
if _, err := stateConf.WaitForStateContext(ctx); err != nil {
302+
return fmt.Errorf("waiting for %s to finish provisioning: %+v", id, err)
303+
}
304+
305+
return nil
306+
}

0 commit comments

Comments
 (0)