When applying changes to **, provider produced an unexpected new value: name: was cty.StringVal("**"), but now null

Hi,

I have created a provider where when i set an attribute name the api does not return the same attribute after a POST so how to solve this issue?
My schema for the name attribute looks like this

"name": schema.StringAttribute{
Computed:    true,
Optional:    true,
Description: "The name of the resource. ",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},

Hi @thulasirajkomminar,

I think you are describing a situation where the API includes a property that you can specify when creating/changing the object but that you cannot retrieve from the API for an existing object. Is that right?

If so, in that case the typical pattern is to rely on the Terraform state to remember the value of the attribute that Terraform most recently knew, and to assume that the value won’t change outside of Terraform. Specifically, when you implement the “read” operation to update the state to match the remote object, you’d just copy the value of this attribute from the prior state directly into the result, which then appears to Terraform as the value not having changed.

This does mean that if someone changes the value outside of Terraform then Terraform won’t be able to detect it, but that’s unavoidable for a value that cannot be read.

Hi
To be more clear. During a create I send a property called name to the api and the api response after creating does not include the same property. In this scenario there is no way to read from the state. So the possible workaround I can think of is use the user specified config(HCL) and assign it to the state after all CRUD operation. So, my question is there a better way?

Scenario 1: In the payload I send name attribute but in the response, I do not get the attribute back.
Payload

{
   "orgID":"***",
   "description":"postman",
   "permissions":[
      {
         "action":"read",
         "resource":{
           "name": "measurements",
            "orgID":"***",
            "type":"buckets"
         }
      },
      {
         "action":"write",
         "resource":{
            "name": "measurements",
            "orgID":"***",
            "type":"buckets"
           
         }
      }
   ]
}

Response:

{
    "orgID": "***",
    "permissions": [
        {
            "action": "read",
            "resource": {
                "type": "buckets",
                "orgID": "***",
                "org": "***"
            }
        },
        {
            "action": "write",
            "resource": {
                "type": "buckets",
                "orgID": "***",
                "org": "***"
            }
        }
    ],
    "createdAt": "2024-04-13T10:02:06.866838962Z",
    "updatedAt": "2024-04-13T10:02:06.866838962Z"
}

Scenario 2: In the payload I do not set name attribute but in the response i get it back.
Payload:

{
    "orgID":"***",
    "description":"postman",
    "permissions":[
       {
          "action":"read",
          "resource":{
            "id": "***",
             "orgID":"***",
             "type":"buckets"
          }
       },
       {
          "action":"write",
          "resource":{
             "id": "***",
             "orgID":"***",
             "type":"buckets"
            
          }
       }
    ]
 }

Response:

{
    "id": "***",
    "description": "postman",
    "orgID": "***",
    "org": "cb",
    "permissions": [
        {
            "action": "read",
            "resource": {
                "type": "buckets",
                "id": "***",
                "orgID": "***",
                "name": "signals",
                "org": "cb"
            }
        },
        {
            "action": "write",
            "resource": {
                "type": "buckets",
                "id": "***",
                "orgID": "***",
                "name": "signals",
                "org": "***"
            }
        }
    ],
    "createdAt": "2024-04-13T10:05:13.862282089Z",
    "updatedAt": "2024-04-13T10:05:13.862282089Z"
}

How can I handle this in the provider framework? If the HCL config does not have name attribute use from the api response but if it is set, then use it from HCL config coz it is not available from the api response.

Hi @thulasirajkomminar :wave:

For the Create method you have access to the Plan in the CreateRequest. If the name value is defined in the configuration, this will appear in the plan and you must set this value in state otherwise you will encounter the sort of error that you describe.

func (e *playgroundResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"name": schema.StringAttribute{
				Computed:    true,
				Optional:    true,
				Description: "The name of the resource. ",
				PlanModifiers: []planmodifier.String{
					stringplanmodifier.UseStateForUnknown(),
				},
			},
		},
	}
}

type playgroundResourceData struct {
	name types.String `tfsdk:"name"`
}

func (e *playgroundResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
	var data playgroundResourceData

	diags := req.Plan.Get(ctx, &data)
	resp.Diagnostics.Append(diags...)

	if resp.Diagnostics.HasError() {
		return
	}

	// API call .........
	// Populate rest of data model ......   

	diags = resp.State.Set(ctx, &data)
	resp.Diagnostics.Append(diags...)
}

Hey @bendbennett
Thats how I do it the way you described, but the problem is when I set the state after making the api call the name attribute is not found from the api response(as i described above) which trows the error. How can I handle this situation?
My codebase terraform-provider-influxdb/internal/provider/authorization_resource.go at main · komminarlabs/terraform-provider-influxdb · GitHub

It looks like the value you are assigning to plan.Permissions is generated as follows in the Create method:

	permissionsResult := getPermissions(*apiResponse.Permissions)
	plan.Permissions = permissionsResult

The getPermissions() function is being passed the response from the API which does not contain the name value from the Plan supplied to the Create method in the CreateRequest. I believe that you will need to amend the logic so that the name value supplied in the Plan is used in order to maintain the data consistency that Terraform is expecting.

Thanks, I will try this out and let you know.