Terraform Framework Import - diff between null and empty string

Hi , I have this schema:

Blueprint struct {
		Meta
		Identifier            string                                  `json:"identifier,omitempty"`
		Title                 string                                  `json:"title,omitempty"`
		Icon                  string                                  `json:"icon,omitempty"`
		Description           string                                  `json:"description,omitempty"`
		Schema                BlueprintSchema                         `json:"schema"`
		MirrorProperties      map[string]BlueprintMirrorProperty      `json:"mirrorProperties"`
		CalculationProperties map[string]BlueprintCalculationProperty `json:"calculationProperties"`
		ChangelogDestination  *ChangelogDestination                   `json:"changelogDestination,omitempty"`
		Relations             map[string]Relation                     `json:"relations"`
	}

and this model:

type BlueprintModel struct {
	ID                    types.String                        `tfsdk:"id"`
	Identifier            types.String                        `tfsdk:"identifier"`
	Title                 types.String                        `tfsdk:"title"`
	Icon                  types.String                        `tfsdk:"icon"`
	Description           types.String                        `tfsdk:"description"`
	CreatedAt             types.String                        `tfsdk:"created_at"`
	CreatedBy             types.String                        `tfsdk:"created_by"`
	UpdatedAt             types.String                        `tfsdk:"updated_at"`
	UpdatedBy             types.String                        `tfsdk:"updated_by"`
	ChangelogDestination  *ChangelogDestinationModel          `tfsdk:"changelog_destination"`
	Properties            *PropertiesModel                    `tfsdk:"properties"`
	Relations             map[string]RelationModel            `tfsdk:"relations"`
	MirrorProperties      map[string]MirrorPropertyModel      `tfsdk:"mirror_properties"`
	CalculationProperties map[string]CalculationPropertyModel `tfsdk:"calculation_properties"`
}

When I run terraform import, terraform doesn’t know how to disguising the property description between empty description and description with “” value.

This is the function that I read the blueprint:

func (c *PortClient) ReadBlueprint(ctx context.Context, id string) (*Blueprint, int, error) {
	pb := &PortBody{}
	url := "v1/blueprints/{identifier}"
	resp, err := c.Client.R().
		SetContext(ctx).
		SetHeader("Accept", "application/json").
		SetResult(pb).
		SetPathParam("identifier", id).
		Get(url)
	if err != nil {
		return nil, resp.StatusCode(), err
	}
	if !pb.OK {
		return nil, resp.StatusCode(), fmt.Errorf("failed to read blueprint, got: %s", resp.Body())
	}
	return &pb.Blueprint, resp.StatusCode(), nil
}

Is there anything I can do?

It is not clear what you mean by this.

The code you have shown is your own and doesn’t interface with Terraform at all.

Hi @dvirsegev!

I think you meant to say that Terraform doesn’t know how to distinguish between an empty and a null description. Is that right?

There are unfortunately some historical oddities here. Terraform v0.11 and earlier did not have a concept of null and so providers written with the legacy plugin SDK (which was designed for Terraform v0.11) basically cannot produce null at all, and so there are some small hacks in the Terraform CLI UI layer to compensate for that by treating a difference between an empty string and a null value as equal in some (but not all cases).

I’m not sure if that’s what you’ve observed that caused you to ask this question, though.

That UI quirk aside, importing in Terraform is a slightly lossy operation because Terraform’s resource instance lifecycle normally expects that all objects sent to a provider are results from previous runs of that same provider, whereas importing violates that assumption and requires a provider to produce a hypothetical result it might have produced even though the provider probably didn’t actually create this object.

In practice that often means that you’ll need to make some somewhat-arbitrary decisions about what to return during import in situations where there is more than one valid way to describe the same situation. null vs. empty string is one example; other examples might be:

  • Remote API is case-insensitive, so the provider must make an arbitrary decision about what case to use in the import result.
  • The API includes a JSON string, so the provider must make an arbitrary decision about how to format that JSON string (whether to include newlines and indentation, for example) in the import result.

The good news is that it doesn’t really matter which representation you choose to use during import as long as your provider has logic to ignore irrelevant differences during the “read” and “plan” operations. You are using the Terraform plugin framework, and the framework’s mechanism to deal with that is semantic equality, which allows defining a derived type that has a custom rule for deciding whether two values are equal or not.

If you define a type that considers null and "" to be equal then you can write your import function to populate whichever one seems most likely to match how a human author would’ve written the configuration (typically null, which represents omitting the argument entirely) and then rely on the semantic equality rule to handle situations where the user has explicitly written description = "" (or something to that effect) in their configuration, so the provider won’t consider that to be a meaningful change.

As far as I know there isn’t yet a reusable “string where the empty string is equivalent to null” custom type, so you may need to write it yourself.

1 Like