Terraform提供程序应如何处理服务器端应用的默认值?

Context: I am implementing (my first) Terraform plugin/provider as a wrapper towards existing public API.

One of the create operations in the API specifies an integer field which takes positive values or -1 serving as a default. If you specify -1 in the create API call, the value gets replaced by some default on the server side (say field = 1000) and is stored as 1000 from now on.

If I present this to my Terraform plugin (terraform apply):

resource "something" "mysomething" {
  name  = "someName"
  field = -1
}

the call is not idempotent. Terraform continues to see a drift and subsequently offers:

  # something.mysomething will be updated in-place
  ~ resource "something" "mysomething" {
        id               = "165-1567498530352"
        name             = "someName"
      ~ field            = 1000 -> -1
    }

Plan: 0 to add, 1 to change, 0 to destroy.

How to deal with such API?

You can use the DiffSuppressFunc flag on schema attributes to conditionally suppress a diff so that Terraform doesn't choose to do anything with the diff.

Something like this should work for you:

package something

import (
    "github.com/hashicorp/terraform/helper/schema"
)

func somethingSomething() *schema.Resource {
    return &schema.Resource{
        // ...
        Schema: map[string]*schema.Schema{
            // ...
            "field": {
                Type:     schema.TypeInt,
                Optional: true,
                Default:  -1,
                DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
                    if new == "-1" {
                        return true
                    }
                    return false
                },
            },
        },
    }
}

Martin's answer provides probably a better alternative by using the Computed flag and making the attribute Optional. For this to work nicely you'd want to prevent people from specifying -1 as a value for this which you can do with a ValidateFunc, using the IntAtLeast validator from the list of predefined validations in the core SDK:

package something

import (
    "github.com/hashicorp/terraform/helper/schema"
    "github.com/hashicorp/terraform/helper/validation"
)

func somethingSomething() *schema.Resource {
    return &schema.Resource{
        // ...
        Schema: map[string]*schema.Schema{
            // ...
            "field": {
                Type:         schema.TypeInt,
                Optional:     true,
                Computed:     true,
                ValidateFunc: validation.IntAtLeast(1),
            },
        },
    }
}

The Terraform SDK includes a special schema flag Computed, which means "if there is no value given in the configuration then a default value will be selected at apply time".

That seems to match with your use-case here. If you unset Default and set Computed: true instead -- retaining Optional: true to indicate that the user can optionally set it -- then you can activate that behavior.

If you're able to predict the final "computed" value during the plan step, before creating or updating anything, then you should implement CustomizeDiff for the resource and use d.Set to provide the value, and then Terraform can take it into account to produce a more complete plan.

If not, then you can leave it unset during planning (in Terraform terms, its value will be "unknown") and then call d.Set in the Create and Update functions instead, with the value appearing as (known after apply) in the plan.

When using this mechanism, it's important to be self-consistent: if you provide a known value during planning using CustomizeDiff then that must exactly match a final value selected during Create or Update. If you aren't consistent then any references to this attribute in other expressions will lead to errors during apply when Terraform Core verifies that the final changes are consistent with what was planned.


There is a currently caveat with this approach: due to API design limitations of the Terraform SDK today, provider code can't tell when a value that was previously set in configuration is now no longer set. Or, to put it another way, the SDK can't tell whether the value already stored was selected by explicit configuration or was populated by the provider as a default during apply.

For this reason, the last value set in the configuration will be "sticky" if the user unsets it, and the provider will not be able to adjust that value back to the server-provided default automatically.

That caveat is often not a big deal in practice, but is worth noting in case it does matter in your specific situation. A subsequent version of the SDK is likely to provide a mechanism to ask if a particular value is set in configuration, separately from what is stored in the state.