func()

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