func()

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
		}
	}
}