in internal/services/azapi_resource.go [419:576]
func (r *AzapiResource) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) {
var config, state, plan *AzapiResourceModel
response.Diagnostics.Append(request.Config.Get(ctx, &config)...)
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
if response.Diagnostics.HasError() {
return
}
// destroy doesn't need to modify plan
if config == nil {
return
}
defer func() {
response.Plan.Set(ctx, plan)
}()
// Output is a computed field, it defaults to unknown if there's any plan change
// It sets to the state if the state exists, and will set to unknown if the output needs to be updated
if state != nil {
plan.Output = state.Output
}
azureResourceType, apiVersion, err := utils.GetAzureResourceTypeApiVersion(config.Type.ValueString())
if err != nil {
response.Diagnostics.AddError("Invalid configuration", fmt.Sprintf(`The argument "type" is invalid: %s`, err.Error()))
return
}
resourceDef, _ := azure.GetResourceDefinition(azureResourceType, apiVersion)
// for resource group, if parent_id is not specified, set it to subscription id
if config.ParentID.IsNull() && strings.EqualFold(azureResourceType, arm.ResourceGroupResourceType.String()) {
plan.ParentID = types.StringValue(fmt.Sprintf("/subscriptions/%s", r.ProviderData.Account.GetSubscriptionId()))
}
if name, diags := r.nameWithDefaultNaming(config.Name); !diags.HasError() {
plan.Name = name
// replace the resource if the name is changed
if state != nil && !state.Name.Equal(plan.Name) {
response.RequiresReplace.Append(path.Root("name"))
}
} else {
response.Diagnostics.Append(diags...)
return
}
// if the config identity type and identity ids are not changed, use the state identity
if !config.Identity.IsNull() && state != nil && !state.Identity.IsNull() {
configIdentity := identity.FromList(config.Identity)
stateIdentity := identity.FromList(state.Identity)
if configIdentity.Type.Equal(stateIdentity.Type) && configIdentity.IdentityIDs.Equal(stateIdentity.IdentityIDs) {
plan.Identity = state.Identity
}
}
isNewResource := state == nil
if !dynamic.IsFullyKnown(plan.Body) || isNewResource || !plan.Identity.Equal(state.Identity) ||
!plan.Type.Equal(state.Type) ||
!plan.ResponseExportValues.Equal(state.ResponseExportValues) || !dynamic.SemanticallyEqual(plan.Body, state.Body) {
plan.Output = basetypes.NewDynamicUnknown()
}
if !dynamic.IsFullyKnown(plan.Body) {
if config.Tags.IsNull() {
plan.Tags = basetypes.NewMapUnknown(types.StringType)
}
if config.Location.IsNull() {
plan.Location = basetypes.NewStringUnknown()
}
}
// Set output as unknown to trigger a plan diff, if ephemral body has changed
diff, diags := ephemeralBodyChangeInPlan(ctx, request.Private, config.SensitiveBody)
if response.Diagnostics = append(response.Diagnostics, diags...); response.Diagnostics.HasError() {
return
}
if diff {
tflog.Info(ctx, `"sensitive_body" has changed`)
plan.Output = types.DynamicUnknown()
}
if dynamic.IsFullyKnown(plan.Body) {
plan.Tags = r.tagsWithDefaultTags(config.Tags, state, config.Body, resourceDef)
if state == nil || !state.Tags.Equal(plan.Tags) {
plan.Output = basetypes.NewDynamicUnknown()
}
// locationWithDefaultLocation will return the location in config if it's not null, otherwise it will return the default location if it supports location
plan.Location = r.locationWithDefaultLocation(config.Location, plan.Location, state, config.Body, resourceDef)
if state != nil && location.Normalize(state.Location.ValueString()) != location.Normalize(plan.Location.ValueString()) {
// if the location is changed, replace the resource
response.RequiresReplace.Append(path.Root("location"))
}
// Check if any paths in replace_triggers_refs have changed
if state != nil && plan != nil && !plan.ReplaceTriggersRefs.IsNull() {
refPaths := make(map[string]string)
for pathIndex, refPath := range AsStringList(plan.ReplaceTriggersRefs) {
refPaths[fmt.Sprintf("%d", pathIndex)] = refPath
}
// read previous values from state
stateData, err := dynamic.ToJSON(state.Body)
if err != nil {
response.Diagnostics.AddError("Invalid state body configuration", err.Error())
return
}
var stateModel interface{}
err = json.Unmarshal(stateData, &stateModel)
if err != nil {
response.Diagnostics.AddError("Invalid state body configuration", err.Error())
return
}
previousValues := flattenOutputJMES(stateModel, refPaths)
// read current values from plan
planData, err := dynamic.ToJSON(plan.Body)
if err != nil {
response.Diagnostics.AddError("Invalid plan body configuration", err.Error())
return
}
var planModel interface{}
err = json.Unmarshal(planData, &planModel)
if err != nil {
response.Diagnostics.AddError("Invalid plan body configuration", err.Error())
return
}
currentValues := flattenOutputJMES(planModel, refPaths)
// compare previous and current values
if !reflect.DeepEqual(previousValues, currentValues) {
response.RequiresReplace.Append(path.Root("body"))
}
}
}
if r.ProviderData.Features.EnablePreflight && isNewResource {
parentId := plan.ParentID.ValueString()
if parentId == "" {
placeholder, err := preflight.ParentIdPlaceholder(resourceDef, r.ProviderData.Account.GetSubscriptionId())
if err != nil {
return
}
parentId = placeholder
}
name := plan.Name.ValueString()
if name == "" {
name = preflight.NamePlaceholder()
}
err = preflight.Validate(ctx, r.ProviderData.ResourceClient, plan.Type.ValueString(), parentId, name, plan.Location.ValueString(), plan.Body, plan.Identity)
if err != nil {
response.Diagnostics.AddError("Preflight Validation: Invalid configuration", err.Error())
return
}
}
}