in internal/services/azapi_resource.go [600:784]
func (r *AzapiResource) CreateUpdate(ctx context.Context, requestConfig tfsdk.Config, requestPlan tfsdk.Plan, responseState *tfsdk.State, diagnostics *diag.Diagnostics, privateData PrivateData) {
var config, plan, state *AzapiResourceModel
diagnostics.Append(requestConfig.Get(ctx, &config)...)
diagnostics.Append(requestPlan.Get(ctx, &plan)...)
diagnostics.Append(responseState.Get(ctx, &state)...)
if diagnostics.HasError() {
return
}
id, err := parse.NewResourceID(plan.Name.ValueString(), plan.ParentID.ValueString(), plan.Type.ValueString())
if err != nil {
diagnostics.AddError("Invalid configuration", err.Error())
return
}
ctx = tflog.SetField(ctx, "resource_id", id.ID())
isNewResource := responseState == nil || responseState.Raw.IsNull()
ctx = tflog.SetField(ctx, "is_new_resource", isNewResource)
var timeout time.Duration
var diags diag.Diagnostics
if isNewResource {
timeout, diags = plan.Timeouts.Create(ctx, 30*time.Minute)
if diagnostics.Append(diags...); diagnostics.HasError() {
return
}
} else {
timeout, diags = plan.Timeouts.Update(ctx, 30*time.Minute)
if diagnostics.Append(diags...); diagnostics.HasError() {
return
}
}
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// Ensure the context deadline has been set before calling ConfigureClientWithCustomRetry().
client := r.ProviderData.ResourceClient.ConfigureClientWithCustomRetry(ctx, plan.Retry, false)
if isNewResource {
// check if the resource already exists using the non-retry client to avoid issue where user specifies
// a FooResourceNotFound error as a retryable error
_, err = r.ProviderData.ResourceClient.Get(ctx, id.AzureResourceId, id.ApiVersion, clients.NewRequestOptions(AsMapOfString(plan.ReadHeaders), AsMapOfLists(plan.ReadQueryParameters)))
if err == nil {
diagnostics.AddError("Resource already exists", tf.ImportAsExistsError("azapi_resource", id.ID()).Error())
return
}
// 403 is returned if group (or child resource of group) does not exist, bug tracked at: https://github.com/Azure/azure-rest-api-specs/issues/9549
if !utils.ResponseErrorWasNotFound(err) && !(utils.ResponseWasForbidden(err) && isManagementGroupScope(id.ID())) {
diagnostics.AddError("Failed to retrieve resource", fmt.Errorf("checking for presence of existing %s: %+v", id, err).Error())
return
}
}
// build the request body
body := make(map[string]interface{})
if err := unmarshalBody(plan.Body, &body); err != nil {
diagnostics.AddError("Invalid body", fmt.Sprintf(`The argument "body" is invalid: %s`, err.Error()))
return
}
if diagnostics.Append(expandBody(body, *plan)...); diagnostics.HasError() {
return
}
SensitiveBody := make(map[string]interface{})
if err := unmarshalBody(config.SensitiveBody, &SensitiveBody); err != nil {
diagnostics.AddError("Invalid sensitive_body", fmt.Sprintf(`The argument "sensitive_body" is invalid: %s`, err.Error()))
return
}
body = utils.MergeObject(body, SensitiveBody).(map[string]interface{})
if !isNewResource {
// handle the case that identity block was once set, now it's removed
if stateIdentity := identity.FromList(state.Identity); body["identity"] == nil && stateIdentity.Type.ValueString() != string(identity.None) {
noneIdentity := identity.Model{Type: types.StringValue(string(identity.None))}
out, _ := identity.ExpandIdentity(noneIdentity)
body["identity"] = out
}
}
// create/update the resource
lockIds := AsStringList(plan.Locks)
slices.Sort(lockIds)
for _, lockId := range lockIds {
locks.ByID(lockId)
defer locks.UnlockByID(lockId)
}
options := clients.NewRequestOptions(AsMapOfString(plan.CreateHeaders), AsMapOfLists(plan.CreateQueryParameters))
if !isNewResource {
options = clients.NewRequestOptions(AsMapOfString(plan.UpdateHeaders), AsMapOfLists(plan.UpdateQueryParameters))
}
_, err = client.CreateOrUpdate(ctx, id.AzureResourceId, id.ApiVersion, body, options)
if err != nil {
tflog.Debug(ctx, "azapi_resource.CreateUpdate client call create/update resource failed", map[string]interface{}{
"err": err,
})
if isNewResource {
if responseBody, err := client.Get(ctx, id.AzureResourceId, id.ApiVersion, clients.NewRequestOptions(AsMapOfString(plan.ReadHeaders), AsMapOfLists(plan.ReadQueryParameters))); err == nil {
// generate the computed fields
plan.ID = types.StringValue(id.ID())
var defaultOutput interface{}
if !r.ProviderData.Features.DisableDefaultOutput {
defaultOutput = id.ResourceDef.GetReadOnly(responseBody)
defaultOutput = utils.RemoveFields(defaultOutput, volatileFieldList())
}
output, err := buildOutputFromBody(responseBody, plan.ResponseExportValues, defaultOutput)
if err != nil {
diagnostics.AddError("Failed to build output", err.Error())
return
}
plan.Output = output
if bodyMap, ok := responseBody.(map[string]interface{}); ok {
if !plan.Identity.IsNull() {
planIdentity := identity.FromList(plan.Identity)
if v := identity.FlattenIdentity(bodyMap["identity"]); v != nil {
planIdentity.TenantID = v.TenantID
planIdentity.PrincipalID = v.PrincipalID
} else {
planIdentity.TenantID = types.StringNull()
planIdentity.PrincipalID = types.StringNull()
}
plan.Identity = identity.ToList(planIdentity)
}
}
diagnostics.Append(responseState.Set(ctx, plan)...)
}
}
diagnostics.AddError("Failed to create/update resource", fmt.Errorf("creating/updating %s: %+v", id, err).Error())
return
}
clientGetAfterPut := r.ProviderData.ResourceClient.ConfigureClientWithCustomRetry(ctx, plan.Retry, true)
tflog.Debug(ctx, "azapi_resource.CreateUpdate get resource after creation")
responseBody, err := clientGetAfterPut.Get(ctx, id.AzureResourceId, id.ApiVersion, clients.NewRequestOptions(AsMapOfString(plan.ReadHeaders), AsMapOfLists(plan.ReadQueryParameters)))
if err != nil {
if utils.ResponseErrorWasNotFound(err) {
tflog.Info(ctx, fmt.Sprintf("Error reading %q - removing from state", id.ID()))
responseState.RemoveResource(ctx)
return
}
diagnostics.AddError("Failed to retrieve resource", fmt.Errorf("reading %s: %+v", id, err).Error())
return
}
// generate the computed fields
plan.ID = types.StringValue(id.ID())
var defaultOutput interface{}
if !r.ProviderData.Features.DisableDefaultOutput {
defaultOutput = id.ResourceDef.GetReadOnly(responseBody)
defaultOutput = utils.RemoveFields(defaultOutput, volatileFieldList())
}
output, err := buildOutputFromBody(responseBody, plan.ResponseExportValues, defaultOutput)
if err != nil {
diagnostics.AddError("Failed to build output", err.Error())
return
}
plan.Output = output
if bodyMap, ok := responseBody.(map[string]interface{}); ok {
if !plan.Identity.IsNull() {
planIdentity := identity.FromList(plan.Identity)
if v := identity.FlattenIdentity(bodyMap["identity"]); v != nil {
planIdentity.TenantID = v.TenantID
planIdentity.PrincipalID = v.PrincipalID
} else {
planIdentity.TenantID = types.StringNull()
planIdentity.PrincipalID = types.StringNull()
}
plan.Identity = identity.ToList(planIdentity)
}
}
diagnostics.Append(responseState.Set(ctx, plan)...)
writeOnlyBytes, err := dynamic.ToJSON(config.SensitiveBody)
if err != nil {
diagnostics.AddError("Invalid sensitive_body", err.Error())
return
}
diagnostics.Append(ephemeralBodyPrivateMgr.Set(ctx, privateData, writeOnlyBytes)...)
}